The following spec ensures that a Project has a User:
it "requires a user" do
expect(FactoryGirl.build_stubbed(:project, user_id: nil)).to_not be_valid
end
But for some reason I feel compelled to do the following too:
context "user identity" do
let(:temp) { FactoryGirl.build_stubbed(:user) }
subject(:project) { FactoryGirl.build_stubbed(:project, user: temp) }
its(:user){ should == temp }
end
I know I need the first test, but I'm beginning to wonder if the second one is a waste of time, especially since the association is handled by the controller:
#project = current_user.projects.build
Is the second test pointless? Seems like it's just testing my factory more than anything.
Is the second test pointless? Seems like it's just testing my factory more than anything.
I think it is not necessary to test. You test has_many and belongs_to relations from core of Rails.
Related
I was able to make the test work with the following code, but it seems to be weird and I don't totally understand it.
Can somebody tell me if creating the objects this way is the optimal one?
Why do I have to only use let! for the 2nd post_comment_reply creation and why don't I for the rest of the objects?
post_comment.rb
belongs_to :post, touch: true
belongs_to :user
has_many :post_comment_replies, dependent: :destroy
has_many :users, through: :post_comment_replies
def send_post_comment_reply_creation_notification(reply)
post_repliers = ([user] + [post.user] + users).uniq - [ reply.user ]
post_repliers.each do |replier|
Notification.create(recipient_id: replier.id, sender_id: reply.user_id, notifiable: self.post, action: "commented")
end
end
post_comment_spec.rb
describe "instance methods" do
let(:post_user) { create(:user) }
let(:comment_user) { create(:user) }
let(:reply_user) { create(:user) }
let(:reply_user_2) { create(:user) }
let(:post_reader) { create(:user) }
let(:post) { create(:post, user: post_user) }
let(:post_comment) { create(:post_comment, user: comment_user) }
let(:post_comment_reply) { create(:post_comment_reply, post_comment: post_comment, user: reply_user) }
let!(:post_comment_reply_2) { create(:post_comment_reply, post_comment: post_comment, user: reply_user_2) }
it "send_post_comment_reply_creation_notification" do
expect{
post_comment.send_post_comment_reply_creation_notification(post_comment_reply)
}.to change{Notification.count}.by(3)
end
end
let is lazy. If you don't reference it, it doesn't get evaluated and, in your case, side effects don't happen (side effect being the creation of database entry).
let!, on the other hand, is always evaluated.
Why you need a let!: let is lazy (it runs only when referred to); let! is eager (it runs before the test whether referred to or not). Your test needs to create :post_comment_reply twice; the let one works because the test refers to it, but the let! one isn't referred to so it has to be a let!, not a let.
Is it optimal? Your test setup works, but as we discovered it's not as clear as it could be. It also sets a trap for anyone adding more tests to the describe block that contains the let!: that object will be created before every test whether it's needed or not, slowing down all tests and possibly affecting the results.
Instead, I'd delete the let! and write this (lets not shown):
describe '#send_post_comment_reply_creation_notification' do
it "notifies each user who replies to the post_comment" do
create(:post_comment_reply, post_comment: post_comment, user: reply_user_2)
expect { post_comment.send_post_comment_reply_creation_notification(post_comment_reply) }.
to change { Notification.count }.by(3)
end
end
In general, prefer creating factory objects in examples (it blocks) rather than in let! blocks. In fact, prefer creation in examples to let as well, unless you're actually using the let variable in more than one example. (You only showed one example, but I suspect there are really more in the same describe block.) If you're only using a factory object in one test there is no reason to make the reader hunt around your test file for where it's defined, or to define a name available in other tests whether it's used there or not.
For example:
I run a test and all asserts pass, but I run the test again, and in my case, I receive the following error:
Validation failed: Email has already been taken
It seems adding: sequence(:email) {|n| "nobody#{n}#xyz.com" } for factorygirl is pointless
The tests seem to pass sometimes and others fail for errors reasons like these.
Please advise on the problem/solution.
try deleting all the records from tables before running test case.
eg:-
describe User do
before(:each) do
User.delete_all
end
it "validate e-mail" do
(do staff..)
end
end
I´m not sure it is a definitive solution, but i added random numbers to my product references on the factories with factorygirl using the lazy attributes.
Example:
FactoryGirl.define do
factory :product do
reference {"AB"+rand(999).to_s}
description "Test Product"
quantity {(1..9999).to_a.sample}
price_per_unit {((1..999999).to_a.sample)/100.to_f}
end
end
Do you have any "before(:all)" block? maybe you are missing the corresponding "after(:all)" to clean the data. before(:each) acts as a transaction so the database gets cleaned, before(:all) works like a normal query, yout have to handle the cleanup in an after(:all) block.
I recently started learning RoR and TDD, and am having trouble figuring out the best way to handle this scenario.
I have an ActiveRecord model with two fields which share the same validations.
How do I write an RSpec test which utilizes the same tests for the similar fields?
"shared examples" looked like a promising feature to utilize in this scenario, but does not seem to work, as I need to test the entire model but am only passing the individual field to the shared example.
Below is my failed attempt:
describe Trip do
before do
#trip = trip.new(date: '2013-07-01', city_1: "PORTLAND",
city_2: "BOSTON")
end
subject { #trip }
shared_examples "a city" do
describe "when not uppercase" do
before { city = city.downcase }
it { should_not be_valid }
end
end
describe "city_1 must be valid" do
it_should_behave_like "a city" do
let!(:city) { #trip.city_1}
end
end
describe "city_2 must be valid" do
it_behaves_like "a city" do
let!(:city) { #trip.city_2}
end
end
end
This fails because updating the city variable does not update trip model. Is there a way to dynamically tie it back to the model?
BTW, all the tests work on their own if I paste under each field. It just will not work in the context of the shared_example.
Any guidance would be greatly appreciated.
You can perform the assertion within a loop, and use a little metaprogramming, but I would advise against it. Tests should be as simple and straightforward as possible, even if that means having a little duplication. If only two fields are involved, just repeat it.
I'm writing integration tests using Rspec and Capybara. I've noticed that quite often I have to execute the same bits of code when it comes to testing the creation of activerecord options.
For instance:
it "should create a new instance" do
# I create an instance here
end
it "should do something based on a new instance" do
# I create an instance here
# I click into the record and add a sub record, or something else
end
The problem seems to be that ActiveRecord objects aren't persisted across tests, however Capybara by default maintains the same session in a spec (weirdness).
I could mock these records, but since this is an integration test and some of these records are pretty complicated (they have image attachments and whatnot) it's much simpler to use Capybara and fill out the user-facing forms.
I've tried defining a function that creates a new record, but that doesn't feel right for some reason. What's the best practice for this?
There are a couple different ways to go here. First of all, in both cases, you can group your example blocks under either a describe or context block, like this:
describe "your instance" do
it "..." do
# do stuff here
end
it "..." do
# do other stuff here
end
end
Then, within the describe or context block, you can set up state that can be used in all the examples, like this:
describe "your instance" do
# run before each example block under the describe block
before(:each) do
# I create an instance here
end
it "creates a new instance" do
# do stuff here
end
it "do something based on a new instance" do
# do other stuff here
end
end
As an alternative to the before(:each) block, you can also use let helper, which I find a little more readable. You can see more about it here.
The very best practice for your requirements is to use Factory Girl for creating records from a blueprint which define common attributes and database_cleaner to clean database across different tests/specs.
And never keep state (such as created records) across different specs, it will lead to dependent specs. You could spot this kind of dependencies using the --order rand option of rspec. If your specs fails randomly you have this kind of issue.
Given the title (...reusing code in Rspec) I suggest the reading of RSpec custom matchers in the "Ruby on Rails Tutorial".
Michael Hartl suggests two solutions to duplication in specs:
Define helper methods for common operations (e.g. log in a user)
Define custom matchers
Use these stuff help decoupling the tests from the implementation.
In addition to these I suggest (as Fabio said) to use FactoryGirl.
You could check my sample rails project. You could find there: https://github.com/lucassus/locomotive
how to use factory_girl
some examples of custom matchers and macros (in spec/support)
how to use shared_examples
and finally how to use very nice shoulda-macros
I would use a combination of factory_girl and Rspec's let method:
describe User do
let(:user) { create :user } # 'create' is a factory_girl method, that will save a new user in the test database
it "should be able to run" do
user.run.should be_true
end
it "should not be able to walk" do
user.walk.should be_false
end
end
# spec/factories/users.rb
FactoryGirl.define do
factory :user do
email { Faker::Internet.email }
username { Faker::Internet.user_name }
end
end
This allows you to do great stuff like this:
describe User do
let(:user) { create :user, attributes }
let(:attributes) { Hash.new }
it "should be able to run" do
user.run.should be_true
end
it "should not be able to walk" do
user.walk.should be_false
end
context "when user is admin" do
let(:attributes) { { admin: true } }
it "should be able to walk" do
user.walk.should be_true
end
end
end
I am finding it very hard to stub certain attributes of a model on a controller test. I want to make sure to stub as little as possible.
EDIT: I have been demoved of using stubs for such integration. I understood that the stubs won't reach the action call. The correct question would now be:
How can one use mocks and stubs to simulate a certain state in a Rails controller test?
So I've reached something like the following:
Spec
require 'spec_helper'
describe TeamsController do
let(:team) { FactoryGirl.create :team }
context "having questions" do
let(:competition) { FactoryGirl.create :competition }
it "allows a team to enter a competition" do
post(:enter_competition, id: team.id, competition_id: competition.id)
assigns(:enroll).team.should == team
assigns(:enroll).competition.should == competition
end
end
# ...
end
Factories
FactoryGirl.define do
factory :team do
name "Ruby team"
end
factory :competition, class: Competition do
name "Competition with questions"
after_create do |competition|
competition.
stub(:questions).
and_return([
"something"
])
end
end
factory :empty_competition, class: Competition do
name "Competition without questions"
questions []
after_create do |competition|
competition.stub(:questions).and_return []
end
end
end
Production code
class TeamsController < ApplicationController
def enter_competition
#team = Team.find params[:id]
#competition = Competition.find params[:competition_id]
#enroll = #team.enter_competition #competition
render :nothing => true
end
end
class Team < ActiveRecord::Base
def enter_competition competition
raise Competition::Closed if competition.questions.empty?
enroll = Enroll.new team: self, competition: competition
enroll.save
enroll
end
end
When I run the test, the questions attribute comes as being nil and so the test fails in the model when checking for nil.empty?.
Why isn't the stub being used so that the state of that message is correctly used? I expected that #competition.questions would be [ "question" ] but instead I get nil.
The problem you're running into is that stub works on an instance of a Ruby object; it doesn't affect all ActiveRecord objects that represent the same row.
The quickest way to fix your test would be to add this to your test, before the post:
Competition.stub(:find).and_return(competition)
The reason that's necessary is that Competition.find will return a fresh Competition object that doesn't have questions stubbed out, even though it represents the same database row. Stubbing find as well means that it will return the same instance of Competition, which means the controller will see the stubbed questions.
I'd advise against having that stub in your factory, though, because it won't be obvious what's stubbed as a developer using the factory, and because it means you'll never be able to test the real questions method, which you'll want to do in the Competition unit test as well as any integration tests.
Long story short: if you stub out a method on an instance of your model, you also need to stub out find for that model (or whatever class method you're using to find it), but it's not a good idea to have such stubs in a factory definition.
When you call create on FactoryGirl, it creates database records which you then retrieve back in your controller code. So the instances you get (#team, #competition) are pure ActiveRecord, without any methods stubbed out.
Personally I would write you test like this (not touching database at all):
let(:team) { mock_model(Team) }
let(:competition) { mock_model(Competition) }
before do
Team.stub(:find) { team }
Competition.stub(:find) { competition }
end
and then in your test something like this:
it "should call enter_competition on #team with #competition" do
team.should_receive(:enter_competition).with(competition)
post :enter_competition, id: 7, competition_id: 10
I don't really understand what your controller is supposed to do or what are you testing for that matter, sorry :(