Rails –Testing named scopes: test scope results or scope configuration? - ruby-on-rails

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...

Related

How does Rspec 'let' helper work with ActiveRecord?

It said here https://www.relishapp.com/rspec/rspec-core/v/3-5/docs/helper-methods/let-and-let what variable defined by let is changing across examples.
I've made the same simple test as in the docs but with the AR model:
RSpec.describe Contact, type: :model do
let(:contact) { FactoryGirl.create(:contact) }
it "cached in the same example" do
a = contact
b = contact
expect(a).to eq(b)
expect(Contact.count).to eq(1)
end
it "not cached across examples" do
a = contact
expect(Contact.count).to eq(2)
end
end
First example passed, but second failed (expected 2, got 1). So contacts table is empty again before second example, inspite of docs.
I was using let and was sure it have the same value in each it block, and my test prove it. So suppose I misunderstand docs. Please explain.
P.S. I use DatabaseCleaner
P.P.S I turn it off. Nothing changed.
EDIT
I turned off DatabaseCleaner and transational fixtures and test pass.
As I can understand (new to programming), let is evaluated once for each it block. If I have three examples each calling on contact variable, my test db will grow to three records at the end (I've tested and so it does).
And for right test behevior I should use DatabaseCleaner.
P.S. I use DatabaseCleaner
That's why your database is empty in the second example. Has nothing to do with let.
The behaviour you have shown is the correct behaviour. No example should be dependant on another example in setting up the correct environment! If you did rely on caching then you are just asking for trouble later down the line.
The example in that document is just trying to prove a point about caching using global variables - it's a completely different scenario to unit testing a Rails application - it is not good practice to be reliant on previous examples to having set something up.
Lets, for example, assume you then write 10 other tests that follow on from this, all of which rely on the fact that the previous examples have created objects. Then at some point in the future you delete one of those examples ... BOOM! every test after that will suddenly fail.
Each test should be able to be tested in isolation from any other test!

Alternatives to find_by for ROR

I'm working through Michael Hartel's rails tutorial, on 6.3 and need alternative code for the user_spec model. The code that he has is:
let(:found_user) { User.find_by(email: #user.email) }
It looks like I can use where, but unsure of the correct syntax. I tried several variations of the following:
let(:found_user) { User.where(:email => "#user.email")}
I'm sure this is a pretty easy answer, but cant quite get it.
let(:found_user){User.where(email: #user.email).first}
or
let(:found_user){User.find_by_email(#user.email)}
That first one that uses where returns a collection of users that match the where clauses, which is why you would need that .first (It doesn't execute the sql until you grab the records with something like .all, .first, or .each).
I would say it's not the best practice to execute database commands in a unit test though. What are you testing specifically? Is there a reason you need the user to be saved in the database and can't just do something like:
let(:user){User.new(email: 'some email')}
ActiveRecord::Base#find_by is effectively where(options).first, but that's a whole extra call that you needn't make.
Rails also provides mildly deprecated "magic" find_by_<attribute>[and_<attribute>] methods which used method_missing to parse out what was meant based on the name of the method. While the framework does provide these, I caution against using them as they are necessarily slower than "native" methods, and are more resistant to refactoring.
I would recommend sticking with find_by for the general case, and would try to avoid hitting the database in specs and tests.
The factory_girl gem provides a method to create a stubbed version of the class which quacks like a record returned from the database by answering true for persisted? and providing an id.
Alternatively, you can just build a new record without saving it: User.new(attribute: value, ...) and run your tests on that:
it "does some things" do
user = User.new(attributes)
# make user do some things
expect(things).to have_happened
end

How to spec operations that rely on Memcached?

We have a Rails application that we test with RSpec. We want to spec operations that rely on Memcached. What is the best practice to do so?
I thought of doing this by stubbing all calls to Rails.cache. Is this a good idea?
As per #Pan Thomakos suggestion, I'm adding some additional details about one of the scenarios I'm trying to test:
We have the concept of accounts in our system, therefore on every request we retrieve the current user and the current account. Because there are not many accounts in the system, we keep them all in cache and retrieve them from there.
def self.find_by_slug(slug)
Rails.cache.fetch(Account.cache_key_for_slug(slug), :expires_in => 1.day) { super }
end
For this reason, caching in this case isn't just a nice to have behavior, but the expected behavior and the thing I want to test. Therefore turning off caching won't do.
Test without stubbing IMHO!
The sequence would look like this:
Cache.flush # or equivalent
Cache.get(slug).shouldbe null # test cache is empty
Method.find_by_slug(slug).should == 'some value' # test that method words
Cache.get(slug).should == 'some value' # test that cache has value.
Personally, I believe if you have the resources on hand then stubbing SHOULD NOT be used. If you do not have the resources on hand (IE a 3rd party service) then stubbing SHOULD BE used.
The problem with stubbing, is that if you changed the code that you are stubbing, then you won't know if it breaks.
An example in this case would be if you switched from the standard memcache gem, to Dahli?, or some other memcache gem which handed cache misses by returning false, null or some other value differently. I mean really! Cache.set("my_key", false)! :)
A example for switching, would be to leave the ASCII protocol and move to the faster binary protocol.
Memcache is a cheap resource, you can set it up with 1 meg of ram to do this testing. I would even go as far as to say you can do the same for mysql. Anything bigger than mysql, then I would start leaning towards stubbing as the cost to "setup" those resources becomes significant. YMMV.
-daniel
It seems like if you're using Rails.cache.fetch directly your best option is to stub. But if you use the controller helpers (which are now in seperate gems in rails 4), I came across this gem which is helpful https://github.com/avit/rspec-rails-caching

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.

Rails assert that form is valid

What's the best practices way to test that a model is valid in rails?
For example, if I have a User model that validates the uniqueness of an email_address property, how do I check that posting the form returned an error (or better yet, specifically returned an error for that field).
I feel like this should be something obvious, but as I'm quickly finding out, I still don't quite have the vocabulary required to effectively google ruby questions.
The easiest way would probably be:
class UserEmailAddressDuplicateTest < ActiveSupport::TestCase
def setup
#email = "test#example.org"
#user1, #user2 = User.create(:email => #email), User.new(:email => #email)
end
def test_user_should_not_be_valid_given_duplicate_email_addresses
assert !#user2.valid?
end
def test_user_should_produce_error_for_duplicate_email_address
# Test for the default error message.
assert_equal "has already been taken", #user2.errors.on(:email)
end
end
Of course it's possible that you don't want to create a separate test case for this behaviour, in which case you could duplicate the logic in the setup method and include it in both tests (or put it in a private method).
Alternatively you could store the first (reference) user in a fixture such as fixtures/users.yml, and simply instantiate a new user with a duplicate address in each test.
Refactor as you see fit!
http://thoughtbot.com/projects/shoulda/
Shoulda includes macros for testing things like validators along with many other things. Worth checking out for TDD.
errors.on is what you want
http://api.rubyonrails.org/classes/ActiveRecord/Errors.html#M002496
#obj.errors.on(:email) will return nil if field is valid, and the error messages either in a String or Array of Strings if there are one or more errors.
Testing the model via unit tests is, of course, step one. However, that doesn't necessarily guarantee that the user will get the feedback they need.
Section 4 of the Rails Guide on Testing has a lot of good information on functional testing (i.e. testing controllers and views). You have a couple of basic options here: check that the flash has a message in it about the error, or use assert_select to find the actual HTML elements that should have been generated in case of an error. The latter is really the only way to test that the user will actually get the message.

Resources