How do I test associated models with Rspec - ruby-on-rails

In my app I have following models:
class UserApplication < ActiveRecord::Base
has_many :builds, dependent: :destroy
belongs_to :user
end
class Build < ActiveRecord::Base
belongs_to :user_application
end
And User model is generated by Devise.
My question is: is I want to test, say, model validations, should I do it like this:
require 'spec_helper'
describe UserApplication do
it "is invalid without name" do
user_application = UserApplication.new(name: nil)
expect(user_application).to have(1).errors_on(:name)
end
end
Or should I create UserApplication through User? In general, should I bear in mind associations when testing my models, if test example is not connected to relationships?

It seems prudent to have test code parallel app code as closely as possible. That is, if UserApplication will be created via User in the controller, it ought to be done the same way in the test. Furthermore, your UserApplication validations will probably test the association sooner or later anyway, so the test subject should be created in such a way as to be valid. With that in mind, you can set up your tests as follows:
require 'spec_helper'
describe UserApplication do
let(:user) { User.create(user_params) }
before { #user_application = user.user_applications.build(name: 'name') }
subject { #user_application }
describe 'validations' do
context 'when name is missing' do
before { #user_application.name = '' }
it { should_not be_valid }
end
context 'when user_id is missing' do
before { #user_application.user_id = nil }
it { should_not be_valid }
end
# other validations
end
end

You should test validations/associations/fields existence/etc in corresponding specs. I don't see your UserApplication validations, but if I correctly understand you, your specs for this model should look like this(I am using shoulda and shoulda-matchers gems for testing):
require 'spec_helper'
describe UserApplication do
let!(:user_application) { create(:user_application) }
it { should have_many(:builds).dependent(:destroy) }
it { should belong_to(:user) }
it { should validate_presence_of(:name) }
end
I am always creating only the instance of the model I want to test. It is important to test that associations exist and correct, but you don't need to create testing model instance through association.

Related

RSpec with FactoryGirl explicit subject

I'm using RSpec with FactoryGirl within a Ruby on Rails environment for testing.
I want to specify my factories as follows:
factory :user do
role # stub
factory :resident do
association :role, factory: :resident_role
end
factory :admin do
association :role, factory: :admin_role
end
end
And I'd like to do something like this in my spec:
require 'rails_helper'
RSpec.describe User, type: :model do
context "all users" do
# describe a user
# subject { build(:user) }
# it { is_expected.to be_something_or_do_something }
end
context "residents" do
# describe a resident
# subject { build(:resident) }
# it { is_expected.to be_something_or_do_something }
end
context "admins" do
# describe a admin
# subject { build(:admin) }
# it { is_expected.to be_something_or_do_something }
end
end
Can this be done by explicitly setting the subject? When I do, I keep getting duplicate roles errors.
If anyone has any advice or suggestion, it would be greatly appreciated!
But this causes the user_spec.rb to use the :user factory.
No, it does not. Assuming you configured FactoryGirl correctly, RSpec can use whatever factory you'd like "on demand" in any test file. Configuration-wise, in rails_helper.rb throw this in:
RSpec.configure do |config|
# ...
config.include FactoryGirl::Syntax::Methods
# ...
end
Then, in your spec file:
require 'rails_helper'
RSpec.describe User, type: :model do
context "all users" do
let(:user) { create(:user) }
it 'is a user' do
# Here `user` is going to be a user factory
expect(user.unit).not_to be_present
end
end
context "residents" do
let(:user) { create(:resident) }
it 'is a resident' do
# Here `user` is going to be a resident factory
expect(user.unit).to be_present
end
end
context "admins" do
let(:user) { create(:admin) }
it 'is an admin' do
# Here `user` is going to be an admin factory
expect(user.role).to be('admin_role')
end
end
end
In short, you can use create(<factory_name>) on any factory definition that exists in any one of these paths:
test/factories.rb
spec/factories.rb
test/factories/*.rb
spec/factories/*.rb
Note that if you haven't placed the config.include FactoryGirl::Syntax::Methods inside your RSpec.configure, you can still create any factory, by doing FactoryGirl.create(<factory_name>) instead of create(<factory_name>).
I don't think you would want to stop them from auto loading, and I'm not actually sure what your use case is for not allowing them to load?
RSpec automagically fetches the factory for a spec
Rspec loads all the factories into memory when your spec helper loads I believe. Because your using factory inheritence your just loading each of these into memory before your tests run, nothing is being called, no objects are being created or built. They are just ready to use in your tests.
Are you getting a specific error or is there some case I'm not seeing that you need?
I found the solution to my problems here: https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md#associations
What I needed to use in my user factories was association :role, factory: :role, strategy: :build

Why does this spec of my model's uniqueness validation fail when it should pass?

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

Shoulda Matcher with custom validation causing all shoulda validations to fail

I am running into a problem where a custom validation on my model is causing all of the shoulda validations to fail.
Essentially:
class User < ActiveRecord::Base
validates_presence_of :name
validate :some_date_validation
private
def some_date_validation
if date_given > birthday
errors.add(:birthday, "Some sort of error message")
end
end
end
And then in the spec:
require 'rails_helper'
RSpec.describe User, type: :model do
describe "shoulda validations" do
it { should validate_presence_of(:name) }
end
end
This will cause my test to fail because the other validation won't pass. Why is this?
You need to test using an instance of an object which is valid by default.
When you use the implicit subject in your Rspec test, Rspec will create a new instance of the object under test for you using the default initializer. In this case, User.new. This instance will be invalid because neither name is present nor is the custom validation going to pass.
If you are using factories (e.g. factory_girl) then you should create a User factory which sets all the attributes which make the validations pass.
FactoryGirl.define do
factory :user do
name "John Doe"
date_given Time.now
birthday 25.years.ago
end
end
Then use it in your tests
require 'rails_helper'
RSpec.describe User, type: :model do
describe "shoulda validations" do
subject { build(:user) }
it { should validate_presence_of(:name) }
end
end
You've now explicitly set the subject of your tests to be a new instance of User created by your factory. The attributes will be pre-set which means your instance is valid by default, and the tests should now be able to test each individual validation properly.

FactoryGirl creating multiple records

I'm trying to get in the habit of writing specs, however, this is becoming increasingly frustrating.
Assume I have two simple models: User and Story. Each model uses a belongs_to relation. Each model uses a validates :foo_id, presence: true as well.
However, FactoryGirl is creating multiple records.
FactoryGirl.define do
factory :user do
email "foo#bar.com"
password "foobarfoobar"
end # this creates user_id: 1
factory :story do
title "this is the title"
body "this is the body"
user # this creates user_id: 2
end
end
This simple test fails:
require 'rails_helper'
describe Story do
let(:user) { FactoryGirl.create(:user) }
let(:story) { FactoryGirl.create(:story) }
it 'should belong to User' do
story.user = user
expect(story.user).to eq(user)
end
end
What am I missing here? I cannot build a Story factory without a User, yet I need it to be just one User record.
The values you define for each attribute in a factory are only used if you don't specify a value in your create or build call.
user = FactoryGirl.create(:user)
story = FactoryGirl.create(:story, user: user)
When doing something like this you can do:
let(:story) { FactoryGirl.create(:story, user: user) }
Or maybe you can only let the story variable and do:
let(:story) { FactoryGirl.create(:story, user: user) }
let(:user) { User.last}
Yes, it is a feature of factory girl to create the associated user when you create the story.
You can avoid it like this:
require 'rails_helper'
describe Story do
let(:story) { FactoryGirl.create(:story) }
let(:user) { story.user }
it 'should belong to User' do
story.user.should eq user
end
end
This example is setup to be trivially true, but you get the point.

Rspec and testing instance methods

Here is my rspec file:
require 'spec_helper'
describe Classroom, focus: true do
describe "associations" do
it { should belong_to(:user) }
end
describe "validations" do
it { should validate_presence_of(:user) }
end
describe "instance methods" do
describe "archive!" do
before(:each) do
#classroom = build_stubbed(:classroom)
end
context "when a classroom is active" do
it "should mark classroom as inactive" do
#classroom.archive!
#classroom.active.should_be == false
end
end
end
end
end
Here is my Classroom Factory:
FactoryGirl.define do
factory :classroom do
name "Hello World"
active true
trait :archive do
active false
end
end
end
When the instance method test runs above, I receive the following error: stubbed models are not allowed to access the database
I understand why this is happening (but my lack of test knowledge/being a newb to testing) but can't figure out how to stub out the model so that it doesn't hit the database
Working Rspec Tests:
require 'spec_helper'
describe Classroom, focus: true do
let(:classroom) { build(:classroom) }
describe "associations" do
it { should belong_to(:user) }
end
describe "validations" do
it { should validate_presence_of(:user) }
end
describe "instance methods" do
describe "archive!" do
context "when a classroom is active" do
it "should mark classroom as inactive" do
classroom.archive!
classroom.active == false
end
end
end
end
end
Your archive! method is trying to save the model to the database. And since you created it as a stubbed model, it doesn't know how to do this. You have 2 possible solutions for this:
Change your method to archive, don't save it to the database, and call that method in your spec instead.
Don't use a stubbed model in your test.
Thoughtbot provides a good example of stubbing dependencies here. The subject under test (OrderProcessor) is a bona fide object, while the items passed through it are stubbed for efficiency.

Resources