Testing a sweeper with RSpec in Rails - ruby-on-rails

I want to make sure my sweeper is being called as appropriate so I tried adding something like this:
it "should clear the cache" do
#foo = Foo.new(#create_params)
Foo.should_receive(:new).with(#create_params).and_return(#foo)
FooSweeper.should_receive(:after_save).with(#foo)
post :create, #create_params
end
But I just get:
<FooSweeper (class)> expected :after_save with (...) once, but received it 0 times
I've tried turning on caching in the test config but that didn't make any difference.

As you already mentioned caching has to be enabled in the environment for this to work. If it's disabled then my example below will fail. It's probably a good idea to temporarily enable this at runtime for your caching specs.
'after_save' is an instance method. You setup an expectation for a class method, which is why it's failing.
The following is the best way I've found to set this expectation:
it "should clear the cache" do
#foo = Foo.new(#create_params)
Foo.should_receive(:new).with(#create_params).and_return(#foo)
foo_sweeper = mock('FooSweeper')
foo_sweeper.stub!(:update)
foo_sweeper.should_receive(:update).with(:after_save, #foo)
Foo.instance_variable_set(:#observer_peers, [foo_sweeper])
post :create, #create_params
end
The problem is that Foo's observers (sweepers are a subclass of observers) are set when Rails boots up, so we have to insert our sweeper mock directly into the model with 'instance_variable_set'.

Sweepers are Singletons and are instantiated at the beginning of the rspec test. As such you can get to it via MySweeperClass.instance(). This worked for me (Rails 3.2):
require 'spec_helper'
describe WidgetSweeper do
it 'should work on create' do
user1 = FactoryGirl.create(:user)
sweeper = WidgetSweeper.instance
sweeper.should_receive :after_save
user1.widgets.create thingie: Faker::Lorem.words.join("")
end
end

Assuming you have:
a FooSweeper class
a Foo class with a bar attribute
foo_sweeper_spec.rb:
require 'spec_helper'
describe FooSweeper do
describe "expiring the foo cache" do
let(:foo) { FactoryGirl.create(:foo) }
let(:sweeper) { FooSweeper.instance }
it "is expired when a foo is updated" do
sweeper.should_receive(:after_update)
foo.update_attribute(:bar, "Test")
end
end
end

Related

RSpec transaction test persists changes to instance variable in before(:each)

SO...
I have a RSpec testing issue where I am attempting to configure a specific setup (without creating new data, but modifying instance data) and after a test case is ran, the data is not reverted. Please see a simple example below and note that foo is an ActiveRecord object...
require 'rails_helper'
RSpec.describe My::Code do
before(:context) do
#foo = FactoryGirl.create(:foo)
end
after(:context) do
#foo.destroy!
end
let(:foo) { #foo.clone }
describe 'something' do
# Imagine `something` just returns foo.bar, which is an over-
# simplification, but gives you an idea of the problem I am seeing
subject { My::Code.something(foo) }
context 'when foo has different property value' do
before(:each) do
foo.bar = false
end
it { is_expected_to be(false) }
end
context 'when foo has original property value' do
# This will fail, as foo's bar property is still false
it { is_expected_to be(true) }
end
end
...this is the gist of my situation and I cannot figure out why RSpec is not rolling back. A little of my rails_helper file...
config.use_transactional_fixtures = true
...
config.around(:each) do |example|
DatabaseCleaner.cleaning do
example.run
end
end
...any insight is much appreciated.
In Ruby on Rails, using clone, this seems to be the expected behavior:
Identical to Ruby's clone method. This is a “shallow” copy. Be warned that your attributes are not copied. That means that modifying attributes of the clone will modify the original, since they will both point to the same attributes hash. If you need a copy of your attributes hash, please use the dup method.
emphasis mine. So by using clone you are still pointing to the same attributes hash and modifying one modifies the other.
As you have found out, you can reload the object which gets rid of all your changes OR you can call dup instead of clone:
Note that this is a “shallow” copy as it copies the object's attributes only, not its associations.

RSpec returns all objects to be 'nil'

I am new to Rspec. I am writing a test case to cover some action in a model. Here is my rspec code
test_cover_image_spec.rb
require 'spec_helper'
describe Issue do
before :each do
#issue = Issue.joins(:multimedia).uniq.first
binding.pry
end
describe '#release_cover_image' do
context 'While making an issue open' do
it 'should make issue cover in S3 accessible' do
put :update, :id => #issue.id, :issue => #issue.attributes = {:open => '1'}
end
end
end
end
#issue always returns nil. In my debugger also, Issue.all returns an empty array.
Tests usually run in isolation. That means each test needs to set up the objects before running. After the test run common test configurations delete all created data from the test database. That means you need to create your test data before you can use it.
For example like this:
require 'spec_helper'
describe Issue do
# pass all attributes to create a valid issue
let(:issue) { Issue.create(title: 'Foo Bar') }
describe '#release_cover_image' do
context 'While making an issue open' do
it 'should make issue cover in S3 accessible' do
put :update, id: issue.id, issue: { open: '1' }
expect(issue.reload.open).to eq('1')
end
end
end
end
To make this work you have to populate the test database first.
Check out factory_girl gem - it is most often used for easy generating test data.
So (general idea is that) you will have to create few factories:
issues_factory.rb
multimedia_factory.rb
And use them, to generate the issue object prior the test run.
If you're not going to use factory_girl then anyway you should change from creating an issue in before block to using let:
let(:issue) { Issue.create }

Expect to receive always succeeds

There are several cases, where my specs always return true, making the test superfluous...
Take this Achievement model for instance:
class Achievement < ActiveRecord::Base
has_many :stages
def call_name_method
name
end
def name
key
end
end
# for simplicity sake, I'm just testing a method which simply calls another method.
achievement_specs.rb
require 'rails_helper'
describe Achievement do
describe '#call_name_method' do
subject { achievement.call_name_method }
let(:achievement) { create(:achievement) }
it 'calls #name' do
expect(achievement).to receive(:name)
subject
end
end
end
This test succeeds, but I can change it to expect(achievement).to receive(:foobar) and it will still succeed, although I am not calling foobar.
According to this answer, it is the correct syntax, but it somehow never fails. Is this a bug?
I also tried using .to have_received(:call_name_method), but that results in this error:
1) Achievement#call_name_method calls #name
Failure/Error: expect(achievement).to have_received(:call_name_method)
# expected to have received call_name_method, but that object is not a spy or method has not been stubbed.
The problem was in the rails_helper.rb
config.after(:each) do
DatabaseCleaner.clean
RSpec::Mocks.teardown # <-- this line was fault
end
After removing that line the specs worked as expected. A coworker accidentally commited this although it was not needed.
Since all test were 'succeeding', it did not catch our attention until now.

RSpec: expect.to receive fails if object is not referenced directly

In my Rails application I have a User model:
class User
def self.foo
User.all.each{ |user| user.bar }
end
def bar
end
end
In my spec file I want to check that foo calls bar for every user, so far that's what I have:
describe '::foo' do
let!(:users) { Fabricate.times(5, :user) }
it 'calls bar for every user' do
users.each do |user|
expect(user).to receive(:bar)
end
User.foo
end
end
Although the method gets called (I debugged it, so I'm sure of that) the spec is red.
Also I tried to write this code to understand where the problem was:
let!(:user) { Fabricate(:user) }
it 'fails' do
expect(user).to receive(:bar)
User.first.bar
end
it 'pass' do
expect(user).to receive(:bar)
user.bar
end
It seems that if I reference my instance directly it works, if I obtain it from the DB the expectation doesn't work.
I use mongoid, not sure if this is relevant.
I believe it cannot be done due to how RSpec works: When you set an expectation, RSpec essentially 'wraps' the object so that it can keep track of the messages it receives.
But when the implementation code fetches records from the database, they are not wrapped, so RSpec isn't able to record their messages.
RSpec does have a method allow_any_instance_of which can help in some cases, but its use is discouraged, and don't think it would be suitable here.
In this situation, I would suggest stubbing User.all to return some doubles (two should be sufficient). You can then verify that bar is called on each one.

Best practice for reusing code in Rspec?

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

Resources