Injecting RSpec mocks to avoid allow_any_instance_of - ruby-on-rails

I have a Rails project with models Partner, Department, Service. The partner and department are seeded, the service is created dynamically when #service is called on an instance of Department. I'm looking to test the #ports functionality of Service in request specs. My first attempt
before do
allow_any_instance_of(Service).to receive(:ports).and_return(ports)
end
context 'no ports' do
let(:ports) { [] }
:
it 'has status 404' do
get :show, params: params
expect(response).to have_http_status(:not_found)
end
end
Now this works, but I know that allow_any_instance_of is a code smell, and one should avoid it if possible.
I tried
let(:service) { instance_mock(Service) }
let(:department) { Department.find_by( ... ) }
before do
allow(department).to receive(:service).and_return(service)
allow(service).to receive(:ports).and_return(ports)
end
but that doesn't work, my test department is different to the one found in the model (it has a different object_id, I get that).
So I inject as follows
let(:department) { instance_double(Department) }
let(:service) { instance_double(Service) }
before do
allow(Department).to receive(:find_by).and_return(department)
allow(department).to receive(:service).and_return(service)
allow(service).to receive(:ports).and_return(ports)
end
Which works as expected. I guess properly this should make some assertions on the arguments to #find_by, but even as it stands, it seems a lot more complicated than the "smelly" version, and I'm having difficulty persuading myself that this is better. One could imagine a situation where there might be a lot of allows needed before you finally get to the object where you can inject your test data.
So my question: is this more-complex style of mock injection worth it to get rid of an allow_any_instance_of; or is there a better way that I've not yet considered?

Related

Rspec unit testing without database

Guys bear with me on this. I would highly appreciate any advise provided.
So lets say I have a controller with the below code:
class PersonController < ApplicationController
serialize_with personSerializer
string: title
int: age
def update
authorize(person, can_update?)
person.update(title:title,age:age)
serialize person
end
end
So in terms of Business Logic we have here:
check for authorize
update the object
return the serializer of the object (it's just a json result)
Now I want to test this piece of code with RSpec, but there is no DB to save or receive an object (well there was one earlier, but I want to remove it when running tests).
So far I have tried using nulldb gem to remove the db dependency. But the problem arrives when the created object cannot be retrieved.
If I want to test the authorize (1). It's a function of another gem that tests if the user can even use this controller.
Do I need to completely mock it? If so, how? Which tool to use?
Same for the update, if I do person.update, how should I check if it works if I don't have access to the active record?
Unless this is a system test, you probably shouldn't be be testing #authorize's functionality in your controller tests. Same goes for ActiveRecord#update. Unit tests should test the logic that exists in the unit of code that you are testing, not external dependencies. Therefore a perfectly acceptable solution would be to assert that those methods are called:
it "#update updates a person when can_update? is true" do
# However you are creating person objects w/o a database.
# Can also be any object.
person = ""
title = "Bob"
age = 81
can_update = true
expect_any_instance_of(YourController).to receive(:authorize).with(person, true)
expect(person).to receive(:update).with({ title: title, age: age })
patch update, params: { title: title, age: age }
expect(response).to have_http_status(:ok)
end
If you still want to test the business logic that lives in 3rd party libraries, you can define separate tests that isolate the desired functionality:
describe "authorize" do
it "authorizes a person with permission" do
person = Person.new(name: "Bob", age: 81)
# Replace with what you expect to happen
expect(authorize(person, true)).to be_truthy
end
end
If you are committed to testing your external business logic in the controller, you'll need to manually mock out every method that the third party library calls. I would highly not recommend this approach as it is
brittle: if the library changes you may have to mock out more methods
verbose: you'll be writing a lot of code that does very little
non-specific: you'll be testing external business logic in test that should be focused on which code is rendered from a request
eventually, I used nulldb, and just mock everything.
also used FactroyBot.
There is no easy way to do so, just hard work and mock all the stuff you need. mainly the DB calls.
For the authorize part i mocked the policy used:
allow_any_instance_of(MyPolicy).to receive(:model).and_return(true)

Multiple assertions for single setup in RSpec

I have a few slower specs that I would like to optimise.
The example of such spec looks like:
require 'rspec'
class HeavyComputation
def compute_result
sleep 1 # something compute heavy here
"very big string"
end
end
describe HeavyComputation, 'preferred style, but slow' do
subject { described_class.new.compute_result }
it { should include 'big' }
it { should match 'string' }
it { should match /very/ }
# +50 others
end
This is very readable and I'm happy with it generally, except that every additional spec will add at least 1 second to the total run-time. That is not very acceptable.
(Please let's not discuss the optimisation on the HeavyComputation class as it is outside of the scope of this question).
So what I have to resort to is spec like this:
describe HeavyComputation, 'faster, but ugly' do
subject { described_class.new.compute_result }
it 'should have expected result overall' do
should include 'big'
should match 'string'
should match /very/
# +50 others
end
end
This is obviously much better performance wise because the time to run it will always be nearly constant.
The problem is that failures are very hard to track down and it is not very intuitive to read.
So ideally, I would like to have a mix of both. Something along these lines:
describe HeavyComputation, 'what I want ideally' do
with_shared_setup_or_subject_or_something_similar_with do
shared(:result) { described_class.new.compute_result }
subject { result }
it { should include 'big' }
it { should match 'string' }
it { should match /very/ }
# +50 others
end
end
But unfortunately I cannot see where to even start implementing it. There are multiple potential issues with it (should the hooks be called on shared result is among those).
What I want to know if there is an existing solution to this problem.
If no, what would be the best way to tackle it?
You can use a before(:context) hook to achieve this:
describe HeavyComputation, 'what I want ideally' do
before(:context) { #result = described_class.new.compute_result }
subject { #result }
it { should include 'big' }
it { should match 'string' }
it { should match /very/ }
# +50 others
end
Be aware that before(:context) comes with a number of caveats, however:
Warning: before(:context)
It is very tempting to use before(:context) to speed things up, but we
recommend that you avoid this as there are a number of gotchas, as well
as things that simply don't work.
context
before(:context) is run in an example that is generated to provide group
context for the block.
instance variables
Instance variables declared in before(:context) are shared across all the
examples in the group. This means that each example can change the
state of a shared object, resulting in an ordering dependency that can
make it difficult to reason about failures.
unsupported rspec constructs
RSpec has several constructs that reset state between each example
automatically. These are not intended for use from within before(:context):
let declarations
subject declarations
Any mocking, stubbing or test double declaration
other frameworks
Mock object frameworks and database transaction managers (like
ActiveRecord) are typically designed around the idea of setting up
before an example, running that one example, and then tearing down.
This means that mocks and stubs can (sometimes) be declared in
before(:context), but get torn down before the first real example is ever
run.
You can create database-backed model objects in a before(:context) in
rspec-rails, but it will not be wrapped in a transaction for you, so
you are on your own to clean up in an after(:context) block.
(from http://rubydoc.info/gems/rspec-core/RSpec/Core/Hooks:before)
As long as you understand that your before(:context) hook is outside the normal per-example lifecycle of things like test doubles and DB transactions, and manage the necessary setup and teardown yourself explicitly, you'll be fine -- but others who work on your code base may not be aware of these gotchas.
#Myron Marston gave some inspiration, so my first attempt to implement it in a more or less reusable way ended up with the following usage (note the shared_subject):
describe HeavyComputation do
shared_subject { described_class.new.compute_result }
it { should include 'big' }
it { should match 'string' }
it { should match /very/ }
# +50 others
end
The idea is to only render subject once, on the very first spec instead of in the shared blocks.
It makes it pretty much unnecessary to change anything (since all the hooks will be executed).
Of course shared_subject assumes the shared state with all its quirks.
But every new nested context will create a new shared subject and to some extent eliminates a possibility of a state leak.
More importantly, all we need to do in order to deal the state leaks s(should those sneak in) is to replace shared_subject back to subject. Then you're running normal RSpec examples.
I'm sure the implementation has some quirks but should be a pretty good start.
aggregate_failures, added in version 3.3, will do some of what you're asking about. It allows you to have multiple expectations inside of a spec, and RSpec will run each and report all failures instead of stopping at the first one.
The catch is that since you have to put it inside of a single spec, you don't get to name each expectation.
There's a block form:
it 'succeeds' do
aggregate_failures "testing response" do
expect(response.status).to eq(200)
expect(response.body).to eq('{"msg":"success"}')
end
end
And a metadata form, which applies to the whole spec:
it 'succeeds', :aggregate_failures do
expect(response.status).to eq(200)
expect(response.body).to eq('{"msg":"success"}')
end
See: https://www.relishapp.com/rspec/rspec-core/docs/expectation-framework-integration/aggregating-failures

Rails –Testing named scopes: test scope results or scope configuration?

How should Rails named scopes be tested? Do you test the results returned from a scope, or that your query is configured correctly?
If I have a User class with an .admins method like:
class User < ActiveRecord::Base
def self.admins
where(admin: true)
end
end
I would probably spec to ensure I get the results I expect:
describe '.admins' do
let(:admin) { create(:user, admin: true) }
let(:non_admin) { create(:user, admin: false) }
let(:admins) { User.admins }
it 'returns admin users' do
expect(admins).to include(admin)
expect(admins).to_not include(non_admin)
end
end
I know that this incurs hits to the database, but I didn't really see any other choice if I wanted to test the scope's behaviour.
However, recently I've seen scopes being specced by confirming that they're configured correctly, rather than on the result set returned. For this example, something like:
describe '.admins' do
let(:query) { User.admins }
let(:filter) { query.where_values_hash.symbolize_keys }
let(:admin_filter) { { admin: true } }
it 'filters for admin users' do
expect(filter).to eq(admin_filter) # or some other similar assertion
end
end
Testing the direct innards of a query like this hadn't really occurred to me before, and on face value it is appealing to me since it doesn't touch the database, so no speed hit incurred.
However, it makes me uneasy because:
it's making a black-box test grey(er)
I have to make the assumption that because something is configured a certain way, I'll get the results that my business logic requires
The example I've used is so trivial that perhaps I'd be okay with just testing the configuration, but:
where do you draw the line and say 'the content of this named scope is too complex and requires result confirmation tests over and above just scope configuration testing'? Does that line even exist or should it?
Is there a legitimate/well-accepted/'best practice' (sorry) way to test named scopes without touching the database, or at least touching it minimally, or is it just unavoidable?
Do you use either of the above ways to test your scopes, or some other method entirely?
This question(s) is a bit similar to Testing named scopes with RSpec, but I couldn't seem to find answers/opinions about testing scope results vs scope configuration.
I think you have described the problem very well, and that the best answer, in my opinion is - it depends.
If your scope is trivial, run-of-the-mill where, with some order, etc. there is no real need to test ActiveRecord or the database to make sure they work properly - you can safely assume that they have been correctly implemented, and simply test the structure you expect.
If, on the other hand, your scope (or any query) is compound, or uses advanced features in a complex configuration, I believe that setting up tests that assert its behavior, by using a real live database (which is installed locally, with a small custom-tailored data set) can go a long way in assuring you that your code works.
It will also help you, if and when you decide to change strategies (use that cool new mysql feature, or porting to postgresql), to refactor safely, by checking that the functionality is robust.
This is a much better way than to simply verify the the SQL string is what you typed there...

Rspec: How can I test an array push?

I am writing some test in Rspec and am trying to push a carrier to a user via a has_and_belongs_to_many association. Below is the test I have written, however the line I have indicated with an arrow does not seem to pass. I realized I have mocked the carrier but not the user and I'm wondering if this is causing an issue with the HABTM association. Is this the issue or is there something else I'm missing? I am new to mocking and stubbing, but trying my best!
describe UsersController do
describe 'get #add_carrier' do
let(:user) { build(:approved_user) }
let(:carrier) { mock_model(Carrier).as_null_object }
before{ Carrier.stub(:find).and_return(carrier) }
it 'associates the Carrier to the User' do
expect(user.carriers).to eq []
user.should_receive(:carriers).and_return([])
--> (user.carriers).should_receive(:push).with(carrier).and_return([carrier])
(user.carriers).push(carrier)
(user.carriers).should include carrier
end
end
end
Stubs are generally used when you want to do a proper unit test and stub out anything but the method under test. Mocks (stubs with expectations) are usually used when you are testing a method that calls a command method (ie a method that has some impact, e.g. altering some data or saving a record) and you want to ensure it is called.
This particular test, given its in a controller, seems to be testing things at the wrong level - it's testing stuff inside the method, not the method itself. Take a look at the rspec docs.
Not knowing the code you're testing, it's a bit tricky to identify exactly how to test. #add_carrier sounds like a method that should simply test whether a carrier is added, so presumably we could test the message expectation. This test also appears to be testing the getter method #carriers, which seems to be a bit much for one unit test (but I completely understand the desire to have it there).
Also note that sharing the error you're getting would definitely be helpful.
Anyway, try something like the following:
describe UsersController do
describe 'get #add_carrier' do # Should this really be a GET?
subject { get :add_carrier }
let(:user) { build(:approved_user) }
let(:carrier) { mock_model(Carrier).as_null_object }
before do
controller.stub(:user) { user }
Carrier.stub(:find) { carrier }
end
it "associates the Carrier to the User" do
user.carriers.should_receive(:push).with(carrier).and_call_original
subject
user.carriers.should include carrier
end
end
end
No expectations on the original value of user.carriers (that should be tested in User model). No expectations on the details of how push works - again, should be tested elsewhere. Rather, just confirming that the important command message is called. I'm not 100% sure we should even be doing #and_call_original and confirming the results, as those are things we can also test in model unit tests (results of Carrier#push), but for peace of mind I included here.
Note this was all written from memory, so please let me know if any of it doesn't work.

When and when not to stub/mock a test

I am making a concerted effort to wrap my head around Rspec in order to move towards more of a TDD/BDD development pattern. However, I'm a long way off and struggling with some of the fundamentals:
Like, when exactly should I be using mocks/stubs and when shouldn't I?
Take for example this scenario: I have a Site model that has_many :blogs and the Blog model has_many :articles. In my Site model I have a callback filter that creates a default set of blogs and articles for every new site. I want to test that code, so here goes:
describe Site, "when created" do
include SiteSpecHelper
before(:each) do
#site = Site.create valid_site_attributes
end
it "should have 2 blogs" do
#site.should have(2).blogs
end
it "should have 1 main blog article" do
#site.blogs.find_by_slug("main").should have(1).articles
end
it "should have 2 secondary blog articles" do
#site.blogs.find_by_slug("secondary").should have(2).articles
end
end
Now, if I run that test, everything passes. However, it's also pretty slow as it's creating a new Site, two new Blogs and three new Articles - for every single test! So I wonder, is this a good candidate for using stubs? Let's give it a go:
describe Site, "when created" do
include SiteSpecHelper
before(:each) do
site = Site.new
#blog = Blog.new
#article = Article.new
Site.stub!(:create).and_return(site)
Blog.stub!(:create).and_return(#blog)
Article.stub!(:create).and_return(#article)
#site = Site.create valid_site_attributes
end
it "should have 2 blogs" do
#site.stub!(:blogs).and_return([#blog, #blog])
#site.should have(2).blogs
end
it "should have 1 main blog article" do
#blog.stub!(:articles).and_return([#article])
#site.stub_chain(:blogs, :find_by_slug).with("main").and_return(#blog)
#site.blogs.find_by_slug("main").should have(1).articles
end
it "should have 2 secondary blog articles" do
#blog.stub!(:articles).and_return([#article, #article])
#site.stub_chain(:blogs, :find_by_slug).with("secondary").and_return(#blog)
#site.blogs.find_by_slug("secondary").should have(2).articles
end
end
Now all the tests still pass, and things are a bit speedier too. But, I've doubled the length of my tests and the whole exercise just strikes me as utterly pointless, because I'm no longer testing my code, I'm just testing my tests.
Now, either I've completely missed the point of mocks/stubs, or I'm approaching it fundamentally wrong, but I'm hoping someone might be able to either:
Improve me tests above so it uses stubs or mocks in a way that actually tests my code, rather than my tests.
Or, tell me if I should even be using stubs here - or whether in fact this is completely unnecessary and I should be writing these models to the test database.
But, I've doubled the length of my tests and the whole exercise just strikes me as utterly pointless, because I'm no longer testing my code, I'm just testing my tests.
This is the key right here. Tests that don't test your code aren't useful. If you can negatively change the code that your tests are supposed to be testing, and the tests don't fail, they're not worth having.
As a rule of thumb, I don't like to mock/stub anything unless I have to. For example, when I'm writing a controller test, and I want to make sure that the appropriate action happens when a record fails to save, I find it easier to stub the object's save method to return false, rather than carefully crafting parameters just so in order to make sure a model fails to save.
Another example is for a helper called admin? that just returns true or false based on whether or not the currently logged-in user is an admin or not. I didn't want to go through faking a user login, so I did this:
# helper
def admin?
unless current_user.nil?
return current_user.is_admin?
else
return false
end
end
# spec
describe "#admin?" do
it "should return false if no user is logged in" do
stubs(:current_user).returns(nil)
admin?.should be_false
end
it "should return false if the current user is not an admin" do
stubs(:current_user).returns(mock(:is_admin? => false))
admin?.should be_false
end
it "should return true if the current user is an admin" do
stubs(:current_user).returns(mock(:is_admin? => true))
admin?.should be_true
end
end
As a middle ground, you might want to look into Shoulda. This way you can just make sure your models have an association defined, and trust that Rails is well-tested enough that the association will "just work" without you having to create an associated model and then counting it.
I've got a model called Member that basically everything in my app is related to. It has 10 associations defined. I could test each of those associations, or I could just do this:
it { should have_many(:achievements).through(:completed_achievements) }
it { should have_many(:attendees).dependent(:destroy) }
it { should have_many(:completed_achievements).dependent(:destroy) }
it { should have_many(:loots).dependent(:nullify) }
it { should have_one(:last_loot) }
it { should have_many(:punishments).dependent(:destroy) }
it { should have_many(:raids).through(:attendees) }
it { should belong_to(:rank) }
it { should belong_to(:user) }
it { should have_many(:wishlists).dependent(:destroy) }
This is exactly why I use stubs/mocks very rarely (really only when I'm going to be hitting an external webservice). The time saved just isn't worth the added complexity.
There are better ways to speed up your testing time, and Nick Gauthier gives a good talk covering a bunch of them - see the video and the slides.
Also, I think a good option is to try out an in-memory sqlite database for your test runs. This should cut down on your database time by quite a bit by not having to hit the disk for everything. I haven't tried this myself, though (I primarily use MongoDB, which has the same benefit), so tread carefully. Here's a fairly recent blog post on it.
I'm not so sure with agreeing on the others. The real problem (as I see it) here, is that you're testing multiple pieces of interesting behavior with the same tests (the finding behavior, and the creation). For reasons on why this is bad, see this talk: http://www.infoq.com/presentations/integration-tests-scam. I'm assuming for the rest of this answer that you want to test that creation is what you want to test.
Isolationist tests often seem unwieldy, but that's often because they have design lessons to teach you. Below are some basic things I can see out of this (though without seeing the production code, I can't do too much good).
For starters, to query the design, does having the Site add articles to a blog make sense? What about a class method on Blog called something like Blog.with_one_article. This then means all you have to test is that that class method has been called twice (if [as I understand it for now], you have a "primary" and "secondary" Blog for each Site, and that the associations are set up (I haven't found a great way to do this in rails yet, I usually don't test it).
Furthermore, are you overriding ActiveRecord's create method when you call Site.create? If so, I'd suggest making a new class method on Site named something else (Site.with_default_blogs possibly?). This is just a general habit of mine, overriding stuff generally causes problems later on in projects.

Resources