I have this spec that I want to translate to MiniTest.
describe User do
subject { build(:user, provider: 'foo') }
# don't validate presence of password when provider is present
it do
should_not validate_presence_of(:password)
end
end
I tried this. I am getting an error of undefined method 'should_not' for UserTest
class UserTest < ActiveSupport::TestCase
def setup
#user = build_stubbed(:user)
end
test "responds to name" do
assert_respond_to #user, :name
end
should validate_presence_of(:password)
test "do not validate presence of password when provider is present" do
build_stubbed(:user, provider: 'foo')
should_not validate_presence_of(:password)
end
end
I want to change the context for one test, where the subject gets a provider attribute, which should disable the presence validator on the password field.
Here's the full error:
UserTest#test_presence_of_password:
NoMethodError: undefined method `should_not' for #<UserTest:0x007feaa82c1c68>
test/models/user_test.rb:45:in `block in <class:UserTest>'
I found that the better way to do this is to revert to good old MiniTest:
test "uniqueness of email with a different provider" do
email_user = create(:user, email: "foo#bar.com")
facebook_user = build_stubbed(:facebook_user, email: "foo#bar.com")
assert facebook_user.valid?, "should be valid with same email if provider is different"
end
Take a look at the minitest-rails-shoulda gem. If you use it I assume the test would look like this:
describe User do
subject { build_stubbed(:user) }
it { must validate_presence_of(:password) }
describe "when a provider is present" do
subject { build_stubbed(:user, provider: 'foo') }
it { wont validate_presence_of(:password) }
end
end
Related
I am learning testing with RSpec. Something is not working with my tests.
My model:
class User < ActiveRecord::Base
has_secure_password
# Validation macros
validates_presence_of :name, :email
validates_uniqueness_of :email, case_sensitive: false
end
My factory:
FactoryGirl.define do
factory :user do
name "Joe Doe"
email "joe#example.com"
password_digest "super_secret_password"
end
end
And my spec:
require 'rails_helper'
RSpec.describe User, type: :model do
user = FactoryGirl.build(:user)
it 'has a valid factory' do
expect(FactoryGirl.build(:user)).to be_valid
end
it { is_expected.to respond_to(:name) }
it { is_expected.to respond_to(:email) }
it { is_expected.to respond_to(:password) }
it { is_expected.to respond_to(:password_confirmation) }
it { expect(user).to validate_presence_of(:name) }
it { expect(user).to validate_presence_of(:email) }
it { expect(user).to validate_presence_of(:password) }
it { expect(user).to validate_uniqueness_of(:email).case_insensitive }
end
I expected this test to pass. But I get this as a result:
Failures:
1) User should validate that :email is case-insensitively unique
Failure/Error: it { expect(user).to validate_uniqueness_of(:email).case_insensitive }
User did not properly validate that :email is case-insensitively unique.
The record you provided could not be created, as it failed with the
following validation errors:
* name: ["can't be blank"]
# ./spec/models/user_spec.rb:18:in `block (2 levels) in <top (required)>'
Finished in 0.34066 seconds (files took 1.56 seconds to load) 9
examples, 1 failure
Failed examples:
rspec ./spec/models/user_spec.rb:18 # User should validate that :email
is case-insensitively unique
What I am missing?
Update
I think that this is a bug: https://github.com/thoughtbot/shoulda-matchers/issues/830
It is because you are declaring it 2 times IMO! First building user then building same user inside expect().
Just use ur first user that you have built with factory-bot like so:
it 'has a valid factory' do
expect(user).to be_valid
end
P.S
It is better to use Faker gem instead of using harcoded instances like you did in factory.rb
Your Variable Is Currently Only Set Once for All Tests
When you write code like:
RSpec.describe User, type: :model do
user = FactoryGirl.build(:user)
end
you aren't building a new user each time you run a new spec. Likewise, using #let is the wrong approach, because it memoizes the variable even between tests. Instead, you need a to use an RSpec before#each block. For example:
describe User do
before do
#user = FactoryGirl.build :user
end
# some specs
end
If you have tests which are persisting you user to the database, and if you have disabled rollback or database cleaning between tests, then your defined factory (as currently written) will certainly fail the uniqueness validation. In such cases, you may want to try:
User.delete_all in your test, or otherwise cleaning your database between tests.
Using FactoryGirl sequences or the Faker gem to ensure that user attributes are actually unique.
USE let
RSpec.describe User, type: :model do
let(:user) { FactoryGirl.build(:user) }
# other what you need
Here is my Spec file:
require 'spec_helper'
describe User, "references" do
it { should have_and_belong_to_many(:roles) }
it { should belong_to(:account_type) }
it { should belong_to(:primary_sport).class_name("Sport") }
it { should belong_to(:school) }
it { should belong_to(:city) }
end
describe User, "factory" do
before(:each) do
#user = FactoryGirl.create(:user)
end
it "is invalid with no email" do
#user.email = nil
#user.should_not be_valid
end
it "is valid with email" do
#user.should be_valid
end
end
Factory:
FactoryGirl.define do
factory :user do
email Faker::Internet.email
password "password"
password_confirmation "password"
agreed_to_age_requirements true
end
end
The part I am trying to "test" for and not sure how to 100% is checking to make sure when a User is created that the email address is not nil.
shoulda provides validation helpers to help you test the validations.
it { should validate_presence_of(:email) }
If you want to use rspec and write your own, then
describe User do
it "should be invalid without email" do
user = FactoryGirl.build(:user, :email => nil)
#user.should_not be_valid
#user.errors.on(:email).should == 'can't be blank' #not sure about the exact message. But you will know when you run the test
end
it "should be valid with email" do
user = FactoryGirl.build(:user, :email => "user#user.com")
#user.should be_valid
end
end
When you run the test, it would read as
User
should be invalid without email
should be valid with email
Giving a good description for your test case is very important, because it kind of acts like a documentation.
I have ran into a quick problem. I am getting a "undefined method `Factory'" for the user_spec page. I believe I have the incorrect syntax for " let(:user) { Factory(:user) }". However I can't come up with one that works.
Failure/Error: let(:user) { Factory(:user) }
NoMethodError:
undefined method `Factory' for #<RSpec::Core::ExampleGroup::Nested_1::Nested_1:0x007fc7ac31e6a8>
If someone can take a look at my code and help me out that would be appreciated as I am almost done testing for the password reset.
user_spec.rb:
require 'spec_helper'
describe User do
describe "#send_password_reset" do
let(:user) { Factory(:user) }
it "generates a unique password_reset_token each time" do
user.send_password_reset
last_token = user.password_reset_token
user.send_password_reset
user.password_reset_token.should_not eq(last_token)
end
it "saves the time the password reset was sent" do
user.send_password_reset
user.reload.password_reset_sent_at.should be_present
end
it "delivers email to user" do
user.send_password_reset
last_email.to.should include (user.email)
end
end
end
the Factory Girl method is the following:
let(:user) { FactoryGirl.create(:user) }
Replace Factory with FactoryGirl, they changed that to remove ambiguity and to let the useful Factory namespace available.
Trying to figure out why my rspec test is failing. Most notable is the Failure message that seems contradictory. Stating I have an ActiveRecord::RecordInvalid error and that is exactly what I'm asserting should happen.
Here is my user.rb
...
validates_presence_of :email
...
Here is my users_spec.rb
...
it "is invalid without email" do
Factory(:user, email: nil).should raise_error(ActiveRecord::RecordInvalid)
end
...
here is the output:
Failures:
1) User a user (in general) is invalid without email
Failure/Error: Factory(:user, email: nil).should raise_error(ActiveRecord::RecordInvalid)
ActiveRecord::RecordInvalid:
Validation failed: Email is invalid, Email can't be blank
# ./spec/models/user_spec.rb:34:in `block (3 levels) in <top (required)>'
Originally I was testing it this way but it kept failing, so I decided to specify on what error I was expecting.
it "is invalid without email" do
Factory(:user, email: nil).should_not be_valid
end
The reason your code isn't working is that you're trying to create an invalid model before actually testing it for validity. What you want to do is to create a valid model, change something and check that it is invalid, like this:
it "is invalid without email" do
user = Factory(:user)
user.email = nil
user.should_not be_valid
end
I personally like to define my model in a before block, set is as the subject and then change attributes in each spec and check for validity, like this:
before do
#user = FactoryGirl.create(:user)
end
subject { #user }
it "is invalid without email" do
subject.email = nil
should_not be_valid
end
For the record, if you wanted to test that the record creation raised an error (which is definitely not the advisable way to do this), you could do it by wrapping the Factory call in a lambda, like this:
lambda {
Factory(:user, :email => nil)
}.should raise_error(ActiveRecord::RecordInvalid)
I have the following validator:
# Source: http://guides.rubyonrails.org/active_record_validations_callbacks.html#custom-validators
# app/validators/email_validator.rb
class EmailValidator < ActiveModel::EachValidator
def validate_each(object, attribute, value)
unless value =~ /^([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})$/i
object.errors[attribute] << (options[:message] || "is not formatted properly")
end
end
end
I would like to be able to test this in RSpec inside of my lib directory. The problem so far is I am not sure how to initialize an EachValidator.
I am not a huge fan of the other approach because it ties the test too close to the implementation. Also, it's fairly hard to follow. This is the approach I ultimately use. Please keep in mind that this is a gross oversimplification of what my validator actually did... just wanted to demonstrate it more simply. There are definitely optimizations to be made
class OmniauthValidator < ActiveModel::Validator
def validate(record)
if !record.omniauth_provider.nil? && !%w(facebook github).include?(record.omniauth_provider)
record.errors[:omniauth_provider] << 'Invalid omniauth provider'
end
end
end
Associated Spec:
require 'spec_helper'
class Validatable
include ActiveModel::Validations
validates_with OmniauthValidator
attr_accessor :omniauth_provider
end
describe OmniauthValidator do
subject { Validatable.new }
context 'without provider' do
it 'is valid' do
expect(subject).to be_valid
end
end
context 'with valid provider' do
it 'is valid' do
subject.stubs(omniauth_provider: 'facebook')
expect(subject).to be_valid
end
end
context 'with unused provider' do
it 'is invalid' do
subject.stubs(omniauth_provider: 'twitter')
expect(subject).not_to be_valid
expect(subject).to have(1).error_on(:omniauth_provider)
end
end
end
Basically my approach is to create a fake object "Validatable" so that we can actually test the results on it rather than have expectations for each part of the implementation
Here's a quick spec I knocked up for that file and it works well. I think the stubbing could probably be cleaned up, but hopefully this will be enough to get you started.
require 'spec_helper'
describe 'EmailValidator' do
before(:each) do
#validator = EmailValidator.new({:attributes => {}})
#mock = mock('model')
#mock.stub('errors').and_return([])
#mock.errors.stub('[]').and_return({})
#mock.errors[].stub('<<')
end
it 'should validate valid address' do
#mock.should_not_receive('errors')
#validator.validate_each(#mock, 'email', 'test#test.com')
end
it 'should validate invalid address' do
#mock.errors[].should_receive('<<')
#validator.validate_each(#mock, 'email', 'notvalid')
end
end
I would recommend creating an anonymous class for testing purposes such as:
require 'spec_helper'
require 'active_model'
require 'email_validator'
RSpec.describe EmailValidator do
subject do
Class.new do
include ActiveModel::Validations
attr_accessor :email
validates :email, email: true
end.new
end
describe 'empty email addresses' do
['', nil].each do |email_address|
describe "when email address is #{email_address}" do
it "does not add an error" do
subject.email = email_address
subject.validate
expect(subject.errors[:email]).not_to include 'is not a valid email address'
end
end
end
end
describe 'invalid email addresses' do
['nope', '#', 'foo#bar.com.', '.', ' '].each do |email_address|
describe "when email address is #{email_address}" do
it "adds an error" do
subject.email = email_address
subject.validate
expect(subject.errors[:email]).to include 'is not a valid email address'
end
end
end
end
describe 'valid email addresses' do
['foo#bar.com', 'foo#bar.bar.co'].each do |email_address|
describe "when email address is #{email_address}" do
it "does not add an error" do
subject.email = email_address
subject.validate
expect(subject.errors[:email]).not_to include 'is not a valid email address'
end
end
end
end
end
This will prevent hardcoded classes such as Validatable, which could be referenced in multiple specs, resulting in unexpected and hard to debug behavior due to interactions between unrelated validations, which you are trying to test in isolation.
Inspired by #Gazler's answer I came up with the following; mocking the model, but using ActiveModel::Errors as errors object. This slims down the mocking quite a lot.
require 'spec_helper'
RSpec.describe EmailValidator, type: :validator do
subject { EmailValidator.new(attributes: { any: true }) }
describe '#validate_each' do
let(:errors) { ActiveModel::Errors.new(OpenStruct.new) }
let(:record) {
instance_double(ActiveModel::Validations, errors: errors)
}
context 'valid email' do
it 'does not increase error count' do
expect {
subject.validate_each(record, :email, 'test#example.com')
}.to_not change(errors, :count)
end
end
context 'invalid email' do
it 'increases the error count' do
expect {
subject.validate_each(record, :email, 'fakeemail')
}.to change(errors, :count)
end
it 'has the correct error message' do
expect {
subject.validate_each(record, :email, 'fakeemail')
}.to change { errors.first }.to [:email, 'is not an email']
end
end
end
end
One more example, with extending an object instead of creating new class in the spec. BitcoinAddressValidator is a custom validator here.
require 'rails_helper'
module BitcoinAddressTest
def self.extended(parent)
class << parent
include ActiveModel::Validations
attr_accessor :address
validates :address, bitcoin_address: true
end
end
end
describe BitcoinAddressValidator do
subject(:model) { Object.new.extend(BitcoinAddressTest) }
it 'has invalid bitcoin address' do
model.address = 'invalid-bitcoin-address'
expect(model.valid?).to be_falsey
expect(model.errors[:address].size).to eq(1)
end
# ...
end
Using Neals great example as a basis I came up with the following (for Rails and RSpec 3).
# /spec/lib/slug_validator_spec.rb
require 'rails_helper'
class Validatable
include ActiveModel::Model
include ActiveModel::Validations
attr_accessor :slug
validates :slug, slug: true
end
RSpec.describe SlugValidator do
subject { Validatable.new(slug: slug) }
context 'when the slug is valid' do
let(:slug) { 'valid' }
it { is_expected.to be_valid }
end
context 'when the slug is less than the minimum allowable length' do
let(:slug) { 'v' }
it { is_expected.to_not be_valid }
end
context 'when the slug is greater than the maximum allowable length' do
let(:slug) { 'v' * 64 }
it { is_expected.to_not be_valid }
end
context 'when the slug contains invalid characters' do
let(:slug) { '*' }
it { is_expected.to_not be_valid }
end
context 'when the slug is a reserved word' do
let(:slug) { 'blog' }
it { is_expected.to_not be_valid }
end
end
If it's possible to not use stubs I would prefer this way:
require "rails_helper"
describe EmailValidator do
let(:user) { build(:user, email: email) } # let's use any real model
let(:validator) { described_class.new(attributes: [:email]) } # validate email field
subject { validator.validate(user) }
context "valid email" do
let(:email) { "person#mail.com" }
it "should be valid" do
# with this expectation we isolate specific validator we test
# and avoid leaking of other validator errors rather than with `user.valid?`
expect { subject }.to_not change { user.errors.count }
expect(user.errors[:email]).to be_blank
end
end
context "ivalid email" do
let(:email) { "invalid.com" }
it "should be invalid" do
expect { subject }.to change { user.errors.count }
# Here we can check message
expect(user.errors[:email]).to be_present
expect(user.errors[:email].join(" ")).to include("Email is invalid")
end
end
end