I'm trying to get my head around RSpec's incredibly confusing, at least initially, syntax by trying to expand on the default specs that are generated with Rails 3 scaffolding...
I have associated models...very simply:
#clown.rb
class Clown < ActiveRecord::Base
has_many :rabbits
end
#rabbit.rb
class Rabbit < ActiveRecord::Base
belongs_to :clown
end
but I'm having trouble with rabbits_controller.spec.rb. In that the specs fail when I reference, say, clown.name in one of rabbit's views. the stupid simple app works as expected but the specs fail because I haven't stubbed (or mocked?) the clown to respond correctly from the rabbit (or at least that's what I think is happening)?!? How should I be adding a stub (or mocking the clown that the rabbit is associate to?).
existing:
#rabbits.controller.spec.rb
require 'spec_helper'
describe RabbitsController do
def mock_rabbit(stubs={})
(#mock_rabbit ||= mock_model(Rabbit).as_null_object).tap do |rabbit|
rabbit.stub(stubs) unless stubs.empty?
end
end
describe "GET index" do
it "assigns all rabbits as #rabbits" do
Rabbit.stub(:all) { [mock_rabbit] }
get :index
assigns(:rabbits).should eq([mock_rabbit])
end
end
...
IMHO (and there are other points of view) this isn't a mocking or stubbing situation. Mocks and stubs are great for external dependencies (think web service), but this is internal to your application. What you want is something like factory_girl, which will let you create test data without the headaches of something like fixtures or trying to mock out every dependent relationship, which quickly becomes monotonous. factory_girl has great documentation, but briefly here's what your factories might look like:
Factory.define(:clown) do |f|
f.rabbits{|c| [c.assocation(:rabbit)]}
f.name "Pierrot"
end
Factory.define(:rabbit) do |f|
f.association :clown
end
You'd then use them in your test like so:
describe RabbitsController do
describe "GET index" do
it "assigns rabbits" do
#rabbit = Factory(:rabbit)
get :index
assigns[:rabbits].should == [#rabbit]
end
end
end
Related
I am trying to write two RSpec tests for two different problems that are much more advanced that what I'm used to writing.
What I'm trying to test within my controller:
def index
#buildings ||= building_class.active.where(place: current_place)
end
My attempt at writing the RSpec test:
describe 'GET :index' do
it "assigns #buildings" do
#buildings ||= building_class.active.where(place: current_place)
get :index
expect(assigns(:buildings)).to eq([building])
end
end
This test failed and wouldn't even run so I know I'm missing something.
My second test is needing to test the returned value of a class method. Here is what I am needing to test within the controller:
def class_name
ABC::Accountant::Business
end
Here is my attempt at testing this method:
describe "class name returns ABC::Accountant::Business" do
subject do
expect(subject.class_name).to eq(ABC::Accountant::Business)
end
end
For the first test I would do something like this:
First, I would move that .active.where(place: current_place) to a scope (I'm guessing building_class returns Building or something like that):
class Building << ApplicationRecord
scope :active_in, -> (place) { active.where(place: place)
Then it's easier to stub for the test
describe 'GET :index' do
it "assigns #buildings" do
scoped_buildings = double(:buildings)
expect(Building).to receive(:active_in).and_return(scoped_buildings)
get :index
expect(assigns(:buildings)).to eq(scoped_buildings)
end
end
Then your controller will do
#buildings ||= building_class.active_in(current_place)
This way you are testing two things: that the controller actually calls the scope and that the controller assigns the returned value on the #buildings variable (you don't really need to test the actual buidlings, you can test the scope on the model spec).
Personally, I feel like it would be better to do something like #buildings = current_place.active_buildings using the same idea of the scope to test that you are getting the active buildings of the current place.
EDIT: if you can't modify your controller, then the stubbing is a little different and it implies some chaining of methods that I don't like to explicitly test.
scoped_buildings = double(:buildings)
controller.stub_chain(:building_class, :active, :where).and_return(scoped_building)
get :index
expect(assings(:buildings)).to eq scoped_buildings
Note that now your test depends on a specific implementation and testing implementation is a bad practice, one should test behaviour and not implementation.
For the second, I guess something like this should work:
describe ".class_name" do
it "returns ABC::Accountant::Business" do
expect(controller.class_name).to eq(ABC::Accountant::Business)
end
end
IMHO, that the method's name if confusing, class_name gives the idea that it returns a string, you are not returnin a name, you are returning a class. Maybe you can change that method to resource_class or something less confusing.
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 :(
i have a question about testing following line of Rails code:
https://gist.github.com/1289849
in my test code i have something like this(obviously don't works):
https://gist.github.com/1289848
Someone can help me write right test code for this ?
Thanks
To make testing it easier, you should move this logic to a class method on your Client model. I'm also assuming you have a has_many :clients on your user model, which is what your query is implying.
Something like:
class Client
def self.search_by_name(name)
all.where("name LIKE %?%", name).order("name ASC")
end
end
Then in your controller:
#clients = current_user.clients.search_by_name(params[:search])
This will allow you to test in a unit test, rather than with an integration test.
client_spec.rb:
describe Client, 'searching by name' do
let(:current_user) { User.create!(...) }
let!(:client) { Client.create!(:name => 'client name', :user => current_user) }
it 'should find the clients by name' do
Client.search_by_name('client name').should include(client)
end
end
Then your integration test could just stub the search_by_name method and return a collection of mocks, making it easier to test.
I'm trying to decide how to test a method that simply calculates an average of values on associated records. I'm concerned about testing the implementation vs the actual result returned.
Say I have the following models...
class User
has_many :interviews
def interview_grade
interviews.average(:score).round unless interviews.empty?
end
end
class Interview
belongs_to :user
end
And in user_spec.rb I have...
describe "interview_grade" do
let(:user) {User.new}
context "when the user has interviews" do
before { user.stub_chain(:interviews, :empty?){false} }
it "should return an average of the appraisal ratings" do
user.interviews.should_receive(:average).with(:score).and_return(3.2)
user.work_history_grade.should == 3
end
end
context "when the user has no interviews" do
before {Interview.destroy_all}
it "should return nil" do
user.interview_grade.should be_nil
end
end
end
These tests pass but it feels fragile to me. What if interview_grade should actually calculate the sum of the scores (for example). As I'm just testing that a particular chain of methods is called, this passing test wouldn't tell me that the result is actually incorrect.
I have tried stubbing user.interviews in order to setup the available scores for the test to work with but this seems tricky to do in Rails 3 due to the way associations are lazy loaded. i.e. I can't just create an array of Interview objects because it doesn't respond to the average method.
Any advice greatly appreciated.
Coming back to this 3 years later. I would would approach it entirely differently.
The benefit of the code below is that in order to write tests for InterviewGrader I would no longer need to worry about how the scores are attained.
I just give it the scores and test it gives me the correct output.
Also I would never need to worry about the underlying implementation of InterviewGrader. However, if the logic was changed at a later date, the tests would fail.
The new scores method on User would need to be tested separately.
class InterviewGrader
def self.run scores
new(scores).run
end
attr_reader :scores
def initialize(scores)
#scores = scores
end
def run
scores.inject { |sum, score|
sum + score
}.to_f / number_of_scores
end
private
def number_of_scores
scores.length
end
end
class User
has_many :interviews
def scores
interviews.map(&:score)
end
def interview_grade
InterviewGrader.run(scores)
end
end
class Interview
belongs_to :user
end
This is incorrect usage of stubbing and mocking.
In this case you should only test, that interview_grade works, when average returns nil (and this is only case interviews.empty? is used).
The average method is tested by rails itself. round method by ruby tests (i guess). So you not need to test this methods. This is a general idea to test only your own code.
And if you want to test, how interview_grade is calculated, you should create test data (with fixtures or factories). Because you should test separate (in some case) part of system, and in this case separation is wrong: interviews.average and interviews.empty? are dependent in your code, but in spec they independent.
def interview_grade
interviews.average(:score).try(:round)
end
If you rewrite your method in this way, you no need in stubbing and mocking