I am using Ruby on Rails 3.0.9, RSpec-rails 2 and FactoryGirl. I am trying to use the RSpec its feature on a association model (in the following example it is :account) but I have some trouble.
In my spec file I have:
describe User do
describe "Associations" do
let(:user) { Factory(:user, :account => Factory.build(:users_account)) }
it { should respond_to(:account) } # This work correctly
its(:account) { should_not be_nil } # This does NOT work correctly (read below for more information)
end
end
If I run the above code I get the following error:
Failure/Error: its(:account) { should_not be_nil }
expected: not nil
got: nil
How can I make the above code to work so to correctly use the RSpec its feature?
You're missing a ')' after (:users_account).
Beyond that I'm not sure, but you could try to use subject instead of let as in,
subject { Factory.build(:user, :account => Factory.build(:users_account)) }
Related
I have run the tests and it doesn't seem that the user get created by a Factory Girl. Here's what I got:
reports_controller_spec.rb
require 'rails_helper'
RSpec.describe ReportsController do
let(:user) { create :user }
before { sign_in user }
describe 'GET #subjects' do
subject { get :subjects }
it_behaves_like 'template rendering action', :subjects
end
end
factories/users.rb
FactoryGirl.define do
factory :user do
email { Faker::Internet.safe_email }
password { Faker::Internet.password }
end
end
And when I run the test I get this error:
ReportsController
GET #subjects
behaves like template rendering action
example at ./spec/support/shared/template_rendering_action.rb:2 (FAILED - 1)
Failures:
1) ReportsController GET #subjects behaves like template rendering action
Failure/Error: Unable to find matching line from backtrace
NoMethodError:
undefined method `password=' for #<User:0x000000063abd20>
Shared Example Group: "template rendering action" called from ./spec/controllers/reports_controller_spec.rb:11
...
I do not understand why it doesn't work. Anyone could point me to the mistake? Thanks.
Edit: I'm using devise.
If you're using Devise, you need to add the password_confirmation to your :user factory
This is my first rspec test
I was using Hurtl's tutorial and figured that it is outdated.
I want to change this line because its is no longer a part of rspec:
its(:user) { should == user }
I tried to do this:
expect(subject.user).to eq(user)
But get an error
RuntimeError: #let or #subject called without a block
This is my full rspec test if you need it:
require 'spec_helper'
require "rails_helper"
describe Question do
let(:user) { FactoryGirl.create(:user) }
before { #question = user.questions.build(content: "Lorem ipsum") }
subject { #question }
it { should respond_to(:body) }
it { should respond_to(:title) }
it { should respond_to(:user_id) }
it { should respond_to(:user) }
expect(subject.user).to eq(user)
its(:user) { should == user }
it { should be_valid }
describe "accessible attributes" do
it "should not allow access to user_id" do
expect do
Question.new(user_id: user.id)
end.to raise_error(ActiveModel::MassAssignmentSecurity::Error)
end
end
describe "when user_id is not present" do
before { #question.user_id = nil }
it { should_not be_valid }
end
end
Yes, you must be following an outdated version since M. Hartl's Railstutorial book now uses Minitest instead of RSpec.
expect(subject.user).to eq(user)
Does not work since you are calling subject without wrapping it in a it block.
You could rewrite it as:
it "should be associated with the right user" do
expect(subject.user).to eq(user)
end
Or you can use the rspec-its gem which lets you use the its syntax with the current version of RSpec.
# with rspec-its
its(:user) { is_expected.to eq user }
# or
its(:user) { should eq user }
But its still not a particularly valuable test since you are just testing the test itself and not the behaviour of the application.
Also this spec is for an older version (pre 3.5) of rails where mass assignment protection was done on the model level.
You can find the current version of the Rails Turorial book at https://www.railstutorial.org/.
You can't translate its(:user) { should == user } directly into expect(subject.user).to eq(user). You have to surround it with an it block
it 'has a matchting user' do
expect(subject.user).to eq(user)
end
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.
I use:
gem 'rails', '3.2.11'
gem 'rspec-rails', '2.13.2'
gem 'webrat', '0.7.3'
gem 'factory_girl_rails', '4.1.0'
gem 'spork', '~> 0.9.0.rc'
I want to test my HP where I always have a link to a certain user, so the pages controller for HP contains:
#user = User.find(7)
And the view HP contains:
link_to 'See user', #user
The problem is that all tests fail since test database has no user with id 7. I tried:
FactoryGirl.define do
factory :user do
name "Testuser"
id "7"
end
end
... but this doesn't work. There is always the error:
The spec is this:
describe "GET 'home'" do
before(:each) do
FactoryGirl.create(:user)
end
it "should be successful" do
get 'home'
response.should be_success
end
end
Failure/Error: get 'home'
ActiveRecord::RecordNotFound:
Couldn't find User with id=7
The HP is working fine in reality, just the test fails. How can I assure this test is not going to fail?
Based on the Using Factories section of the documentation, I would set the id in the spec itself with something like:
#user = FactoryGirl.create(:user, :id => 7)
What about mocking / stubbing (much faster spec when not saving objects to database):
let(:user) { FactoryGirl.build(:user) }
let(:id) { '1' }
before do
user.stub(:id).and_return(id) # just to illustrate, not necessary if stubbing the find class method
User.stub(:find).and_return(user)
end
it { expect(user.id).to eq(id) } # just to illustrate
it { expect(response).to be_success }
it { expect(assigns(:user)).to eq(user) }
If you still have issue with the user being only instantiated, you can still use the above technique but replace FactoryGirl.build(:user) by FactoryGirl.create(:user)
Not an answer but a suspicition... you probably shouldn't write the spec the way you are doing it.
in your spec do something like:
u=FactoryGirl.create(:user)
User.where('id=?',u.id).count.should == 1
Making your tests dependent on specific ids is a recipe for disaster.
You can set the ID of a user by doing something like
before
u = FactoryGirl.create(:user)
u.update_attribute(:id, 7)
end
However, this is a little odd. You may even run into a case where there are user's 7.
What you could do though, is use Rspec's stubs to get the proper user. For instance you could do something like
let(:user_7) { FactoryGirl.create(:user) }
before { User.stub(:find).with(7).and_return(user_7) }
In your case I am sure you are just checking that the user id is being used instead of a specific ID.
When I write tests I just get the id returned from the database which is far more reliable and closer to what you are doing in reality.
I would write the controller spec something like this.
RSpec.describe MyController, type: :controller do
let(:user) { FactoryGirl.create(:user) }
before do
# login the user here which would resolve which user id is being used
sign_in user #assumes you are using warden test helpers
get :home
end
it "allows the user to the page" do
assert_response :ok
end
end
This should load fine. If you wish to double check that the link is correct you should do that in a feature spec using something like capybara.
I have started my journey with TDD in Rails and have run into a small issue regarding tests for model validations that I can't seem to find a solution to. Let's say I have a User model,
class User < ActiveRecord::Base
validates :username, :presence => true
end
and a simple test
it "should require a username" do
User.new(:username => "").should_not be_valid
end
This correctly tests the presence validation, but what if I want to be more specific? For example, testing full_messages on the errors object..
it "should require a username" do
user = User.create(:username => "")
user.errors[:username].should ~= /can't be blank/
end
My concern about the initial attempt (using should_not be_valid) is that RSpec won't produce a descriptive error message. It simply says "expected valid? to return false, got true." However, the second test example has a minor drawback: it uses the create method instead of the new method in order to get at the errors object.
I would like my tests to be more specific about what they're testing, but at the same time not have to touch a database.
Anyone have any input?
CONGRATULATIONS on you endeavor into TDD with ROR I promise once you get going you will not look back.
The simplest quick and dirty solution will be to generate a new valid model before each of your tests like this:
before(:each) do
#user = User.new
#user.username = "a valid username"
end
BUT what I suggest is you set up factories for all your models that will generate a valid model for you automatically and then you can muddle with individual attributes and see if your validation. I like to use FactoryGirl for this:
Basically once you get set up your test would look something like this:
it "should have valid factory" do
FactoryGirl.build(:user).should be_valid
end
it "should require a username" do
FactoryGirl.build(:user, :username => "").should_not be_valid
end
Here is a good railscast that explains it all better than me:
UPDATE: As of version 3.0 the syntax for factory girl has changed. I have amended my sample code to reflect this.
An easier way to test model validations (and a lot more of active-record) is to use a gem like shoulda or remarkable.
They will allow to the test as follows:
describe User
it { should validate_presence_of :name }
end
Try this:
it "should require a username" do
user = User.create(:username => "")
user.valid?
user.errors.should have_key(:username)
end
in new version rspec, you should use expect instead should, otherwise you'll get warning:
it "should have valid factory" do
expect(FactoryGirl.build(:user)).to be_valid
end
it "should require a username" do
expect(FactoryGirl.build(:user, :username => "")).not_to be_valid
end
I have traditionally handled error content specs in feature or request specs. So, for instance, I have a similar spec which I'll condense below:
Feature Spec Example
before(:each) { visit_order_path }
scenario 'with invalid (empty) description' , :js => :true do
add_empty_task #this line is defined in my spec_helper
expect(page).to have_content("can't be blank")
So then, I have my model spec testing whether something is valid, but then my feature spec which tests the exact output of the error message. FYI, these feature specs require Capybara which can be found here.
Like #nathanvda said, I would take advantage of Thoughtbot's Shoulda Matchers gem. With that rocking, you can write your test in the following manner as to test for presence, as well as any custom error message.
RSpec.describe User do
describe 'User validations' do
let(:message) { "I pitty da foo who dont enter a name" }
it 'validates presence and message' do
is_expected.to validate_presence_of(:name).
with_message message
end
# shorthand syntax:
it { is_expected.to validate_presence_of(:name).with_message message }
end
end
A little late to the party here, but if you don't want to add shoulda matchers, this should work with rspec-rails and factorybot:
# ./spec/factories/user.rb
FactoryBot.define do
factory :user do
sequence(:username) { |n| "user_#{n}" }
end
end
# ./spec/models/user_spec.rb
describe User, type: :model do
context 'without a username' do
let(:user) { create :user, username: nil }
it "should NOT be valid with a username error" do
expect(user).not_to be_valid
expect(user.errors).to have_key(:username)
end
end
end