I try to set a instance variable in a subject before testing validity of model fields. I need to set this variable, because validation is conditional (it is used only for some type of users). So I have something like this:
context "as a user" do
before(:each) do
subject = Organization.new
subject.editor = "user"
end
it { subject.should validate_presence_of :name }
end
But it doesn't work as expected:
Failure/Error: it { subject.should validate_presence_of :description }
RuntimeError:
Organization#editor attr is not set
What did i miss?
subject in your before block is a local variable. It looks like you meant to use an explicit subject:
context "as a user" do
subject { Organization.new }
before(:each) do
subject.editor = "user"
end
# usually, you don't explicitly name the subject in an `it` like this
it { should validate_presence_of :name }
end
Related
I am learning how to test on rails from this tutorial.
On one part of the tutorial, it shows how to write invalid_attribute test:
require 'rails_helper'
RSpec.describe ContactsController, type: :controller do
describe "POST #create" do
context "with valid attributes" do
it "create new contact" do
post :create, contact: attributes_for(:contact)
expect(Contact.count).to eq(1)
end
end
context "with invalid attributes" do
it "does not create new contact" do
post :create, contact: attributes_for(:invalid_contact)
expect(Contact.count).to eq(0)
end
end
end
end
I don't understand where :contact and :invalid_contact point to.
Does :contact points to Contact class? It seems like it from FactoryGirl's gh. If so, then how can I create :invalid_contact since there is no :invalid_contact class?
I have tried post :create, contact: attributes_for(:contact, :full_name => nil) but it still fails.
spec/factories/contacts.rb:
FactoryGirl.define do
factory :contact do
full_name { Faker::Name.name }
email { Faker::Internet.email }
phone_number { Faker::PhoneNumber.phone_number }
address { Faker::Address.street_address }
end
end
First test, with valid attributes pass. On model, there is presence validation validates_presence_of :full_name, :email, :phone_number, :address. What do I add in order to pass "with invalid attributes" test?
The factory will use the class with the same name. So your :contact factory will use the Contact class. You can create a new factory for the invalid contact by specifying the class to use.
factory :invalid_contact, class: Contact do
full_name nil
end
It's also possible to use traits to avoid having two different factories.
FactoryGirl.define do
factory :contact do
full_name { Faker::Name.name }
email { Faker::Internet.email }
phone_number { Faker::PhoneNumber.phone_number }
address { Faker::Address.street_address }
trait :invalid do
full_name nil
end
end
end
Then use it with attributes_for(:contact, :invalid)
The tutorial you link to says:
Following the spec above, write a spec that uses invalid attributes to
create a new contact. This spec should check that the contact is not
created.
So you need to figure out how to test for :invalid_contact using the example for :contact.
You can just add a let in your spec:
Use let to define a memoized helper method. The value will be cached
across multiple calls in the same example but not across examples.
Source: https://www.relishapp.com/rspec/rspec-core/v/3-5/docs/helper-methods/let-and-let
Then your controller spec would look like this:
...
let(:invalid_contact) { create(:contact, name: nil) }
context "with invalid attributes" do
it "does not create new contact" do
post :create, contact: attributes_for(invalid_contact)
expect(Contact.count).to eq(0)
end
end
...
this way #post action params are picked up from invalid_contact
or as #fanta suggested in comments, you can add a trait to your factory. I prefer my method because other people looking at your code will know why invalid_contact should be invalid without looking at the :contacts factory
describe '#messages' do
subject do
FactoryGirl.create :foo,
:type => 'test',
:country => 'US'
end
context 'when is not U.S.' do
before{ allow(subject).to receive(:country).and_return('MX') }
describe '#messages' do
subject { super().messages }
it { is_expected.to include 'This foo was not issued in the United States of America.' }
end
end
end
I'm trying to assign an attribute on the subject... I can't seem to get the incantation correct. Do I need a Double here? I'm not sure how that even works, and I apparently can't decipher the docs. Any help is appreciated.
I think you should define the subject variable as a helper method using let. With this, you are defining a helper method that you can use everywhere in the file.
So, I'll modify your code as follows:
describe '#messages' do
let(:subject) do
FactoryGirl.create :foo,
:type => 'test',
:country => 'US'
end
context 'when is not U.S.' do
before{ allow(subject).to receive(:country).and_return('MX') }
describe '#messages' do
subject { super().messages }
it { is_expected.to include 'This foo was not issued in the United States of America.' }
end
end
end
I think the problem here is one of scope. you are calling the code in the before block before you've set up a subject
you'll need to either move the subject out into the outer context, move the before into the inner describe, or set up some alternative way of calling it so that subject is set up before running the before
I'm writing some RSpec tests for a Rails 3.2 app, and Rspec keeps crashing on this test:
describe Person do
before { #person = Person.new(names: [Factory.create(:name)], DOB: Date.today) }
subject { #person }
[:names,:DOB,:phone,:email,:address1,:address2,
:city,:state,:zip,:notes,:last_seen].each do |value|
it { should respond_to(value) }
end
it { should be_valid }
describe "when no name is present" do
#person.names = []
it {should be_invalid}
end
describe "when no DOB is present" do
#person.DOB = nil
it {should be_invalid}
end
end
When I run RSpec, I get undefined method 'names=' for nil:NilClass (NoMethodError) for this line:
#person.names = []
If I remove the no-name test, then it crashs on #person.DOB = nil
It looks like somehow, #person isn't being set before the describe block. Is there some quirk of RSpec I'm missing?
Edit: The Person model:
class Person < ActiveRecord::Base
has_and_belongs_to_many :names
validates_presence_of :DOB
end
Pretty simple. There's a names_people join table to connect Name and Person. Like I've said, I can usually access name through names=(). It's something about this one test.
Solved it. I had to use the before method again to make changes to #person. For example, I had to use this:
describe "when no name is present" do
before { #person.names = [] }
it {should be_invalid}
end
Instead of this:
describe "when no name is present" do
#person.names = []
it {should be_invalid}
end
Given I have the following class
class listing > ActiveRecord::Base
attr_accessible :address
belongs_to :owner
validates :owner_id, presence: true
validates :address, presence: true
end
Is there a way I can get away with not having to keep associating an owner before I save a listing in my tests in /spec/models/listing_spec.rb, without making owner_id accessible through mass assignment?
describe Listing do
before(:each) do
#owner = Factory :owner
#valid_attr = {
address: 'An address',
}
end
it "should create a new instance given valid attributes" do
listing = Listing.new #valid_attr
listing.owner = #owner
listing.save!
end
it "should require an address" do
listing = Listing.new #valid_attr.merge(:address => "")
listing.owner = #owner
listing.should_not be_valid
end
end
No need to use factory-girl (unless you want to...):
let(:valid_attributes) { address: 'An Address', owner_id: 5}
it "creates a new instance with valid attributes" do
listing = Listing.new(valid_attributes)
listing.should be_valid
end
it "requires an address" do
listing = Listing.new(valid_attributes.except(:address))
listing.should_not be_valid
listing.errors(:address).should include("must be present")
end
it "requires an owner_id" do
listing = Listing.new(valid_attributes.except(:owner_id))
listing.should_not be_valid
listing.errors(:owner_id).should include("must be present")
end
There is if you use factory-girl
# it's probably not a good idea to use FG in the first one
it "should create a new instance given valid attributes" do
listing = Listing.new #valid_attr
listing.owner = #owner
listing.property_type = Factory(:property_type)
listing.save!
end
it "should require an address" do
# But here you can use it fine
listing = Factory.build :listing, address: ''
listing.should_not be_valid
end
it "should require a reasonable short address" do
listing = Factory.build :listing, address: 'a'*245
listing.should_not be_valid
end
I hate to be the voice of dissent here, but you shouldn't be calling save! or valid? at all in your validation spec. And 9 times out of 10, if you need to use factory girl just to check the validity of your model, something is very wrong. What you should be doing is checking for errors on each of the attributes.
A better way to write the above would be:
describe Listing do
describe "when first created" do
it { should have(1).error_on(:address) }
it { should have(1).error_on(:owner_id) }
end
end
Also, chances are you don't want to be checking for the presence of an address, you want to check that it is not nil, not an empty string, and that it is not longer than a certain length. You'll want to use validates_length_of for that.
Ok say I have the following model:
class Country < ActiveRecord::Base
validates_presence_of :name
validates_presence_of :code
end
I'm doing the rspec unit tests for those validations. They look like this:
it "should be invalid without a name" do
country = Country.new(#valid_attributes.except(:name))
country.should_not be_valid
country.errors.on(:name).should == "can't be blank"
country.name = #valid_attributes[:name]
country.should be_valid
end
it "should be invalid without a code" do
country = Country.new(#valid_attributes.except(:code))
country.should_not be_valid
country.errors.on(:code).should == "can't be blank"
country.code = #valid_attributes[:code]
country.should be_valid
end
This doesn't look quite DRY. Is there any gem or plugin that automates this kind of stuff?
I'd like to get something along these lines:
it "should be invalid without a name" do
test_presence_validation :name
end
it "should be invalid without a code" do
test_presence_validation :code
end
There are remarkable for that : http://github.com/carlosbrando/remarkable
After you can do
it { should validate_presence_of :name }
If you're using factory_girl, you can do:
it "should be invalid without a name" do
FactoryGirl.build(:country, name: nil).should_not be_valid
end
One suggestion... don't use the keyword "should" on every spec.
Instead, write: "is invalid without a name"
Try also accept_values_for gem.
It allows to do something like this:
describe User do
subject { User.new(#valid_attributes)}
it { should accept_values_for(:email, "john#example.com", "lambda#gusiev.com") }
it { should_not accept_values_for(:email, "invalid", nil, "a#b", "john#.com") }
end