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)
Related
I'm trying to test my rails application which using Stripe APIs, So I started with models, I'm using Rspec, The model which i want to test is called bank_account.rb inside it there is a function called (create_bank_account) with argument (bank_token) its pseudocode is something like this:
def create_bank_account(bank_token)
# make a Stripe request and save it in local variable
# save needed data in my bank_account table in my DB
end
when i started to test this function, I found that there is an API call inside it, Which is not good, I need my test not to depend on Internet, So after searching I found 'StripeMock` gem, It is useful and i started to use it with Rspec, but I found my self writing a test like this:
it 'with valid bank_token` do
# create a double for bank_account
# using StripeMock to get a faked response for creating
# new bank_account
# expect the doube to receive create_bank_account
# function and response with saving the data inside the DB
end
but after writing this I noticed that I didn't actually run create_bank_account function i faked it, So my questions are:
1- How can i test function that includes API request but run the function it self not faking it?
2- I read a lot about when we use doubles and stubs and what i understood is when a function is not completed, but if the functions is already implemented should i use doubles to avoid something like functions that call APIs?
First and foremost:
Do not create a double for bank_account.
Do not mock/stub bank_account.create_bank_account.
If you do either of these things, in a test that is supposed to be testing behaviour of BankAccount#create_bank_account, then your test is worthless.
(To prove this point, try writing broken code in the method. Your tests should obviously fail. But if you're mocking the method, everything will remain passing!!)
One way or another, you should only be mocking the stripe request, i.e. the behaviour at the boundary between your application and the internet.
I cannot provide a working code sample without a little more information, but broadly speaking you could refactor your code from this:
def create_bank_account(bank_token)
# make a Stripe request and save it in local variable
# save needed data in my bank_account table in my DB
end
To this:
def create_bank_account(bank_token)
stripe_request = make_stripe_request(bank_token)
# save needed data in my bank_account table in my DB
end
private
def make_stripe_request(bank_token)
# ...
end
...And then in your test, you can use StripeMock to only fake the response of BankAccount#make_stripe_request.
If the code is not so easy to refactor(?!), then stubbing the Stripe library directly like this might not be practical. An alternative approach you can always take is use a library like webmock to simply intercept all HTTP calls.
How do I test a method available only to an ActiveRecord relation proxy class in rspec? Like for example sum which would look something like #collection.sum(:attribute)
Here is what I'm trying to do:
#invoice = stub_model(Invoice)
#line_item = stub_model(LineItem, {quantity: 1, cost: 10.00, invoice: #invoice})
#invoice.stub(:line_items).and_return([#line_item])
#invoice.line_items.sum(:cost).should eq(10)
This doesn't work because #invoice.line_items returns a regular array that doesn't define sum in the same way as an ActiveRecord::Relation object does.
Any help is greatly appreciated.
I'm not sure which Rails you are on so I'll use Rails 4.0.x for this example; the principle still holds for Rails 3.x.
TL;DR: You don't want to take this route.
Consider not stubbing model specs
Consider adding domain specific APIs
You are rapidly heading down the road of over mocking/stubbing. I have been down this road, it does not lead to fun. Part of all of this comes down to violating the Law of Demeter. Part of it comes down to using the Rails APIs instead of creating your own domain APIs.
When you request an relation collection from an ActiveRecord model it does not return an Array as you are aware. In Rails 4.0.x, with a has_many association, the class which is returned is: ActiveRecord::Associations::CollectionProxy::ActiveRecord_Associations_CollectionProxy_Model.
Issue #1: Stubbing the wrong return value
Here your return type is an Array. While the actual return type is the ActiveRecord_Associations_CollectionProxy_Model. In stub/mock land, this isn't necessarily a bad thing. However, if you intend to use other calls on the object returned by the stub they need to match the same API contracts. Otherwise, you're not stubbing the same behavior.
In this case, the sum method defined on the AR association proxy actually executes SQL when it runs. The sum method defined on Array is patched in via Active Support. The Array#sum behavior is fundamentally different:
def sum(identity = 0, &block)
if block_given?
map(&block).sum(identity)
else
inject { |sum, element| sum + element } || identity
end
end
As you can see, it sums the elements, not the sum of the requested attribute.
Issue #2: Asserting on your stub'd object
The other main problem you have, is you are attempting to spec that you're stub returns what you stubbed. This doesn't make sense. The point of a stub is to return a canned answer. It's not to assert on how it behaves.
What you wrote isn't fundamentally different from:
invoice = stub_model(Invoice)
line_item = stub_model(LineItem, {quantity: 1, cost: 10.00, invoice: invoice})
invoice.stub(:line_items).and_return([line_item])
invoice.line_items.should eq([line_item])
Unless this is supposed to be a sanity check, it adds no real value to your specs.
Suggestions
I'm not sure what type of spec you are writing here. If this is a more traditional unit test or an acceptance test, then I probably wouldn't stub anything. There isn't necessarily anything wrong with hitting a database at times, especially when the thing you are testing is how you interact with it; which is really what you are doing here.
Another thing you can do is start to use this to create your own specific domain model APIs. All this really means is defining interfaces on objects that make sense for your domain, which may or may not be backed by a DB or other resource.
For example, take your invoice.line_items.sum(:cost).should eq(10), this is clearly testing the Rails AR API. In domain terms it means nothing really. However, invoice.subtotal probably means a lot more to your domain:
# app/models/invoice.rb
class Invoice < ActiveRecord::Base
def subtotal
line_items.sum(:cost)
end
end
# spec/models/invoice_spec.rb
# These are unit specs on the model, which directly works with the DB
# it probably doesn't make sense to stub things here
describe Invoice do
specify "the subtotal is the sum of all line item cost" do
invoice = create(:invoice)
3.times do |i|
cost = (i + 1) * 2
invoice.line_items.create(cost: cost)
end
expect(invoice.subtotal).to eq 12
end
end
Now later, when you use Invoice in some other part of your code, you can easily stub this if you need to:
# spec/helpers/invoice_helper_spec.rb
describe InvoiceHelper do
context "requesting the formatted subtotal" do
it "returns US dollars to two decimal places" do
invoice = double(Invoice, subtotal: 1012)
assign(:invoice, invoice)
expect(helper.subtotal_in_dollars).to eq "$10.12"
end
end
end
So when it is ok to stub model specs? Well, that's really a judgement call, and will vary from person to person, and code base to code base. However, just because something is in app/models doesn't mean it has to be an ActiveRecord model. In those cases, it's potentially fine to stub domain APIs on collaborators.
EDIT: create vs build
In the example above I used create(:invoice) and invoice.line_items.create(cost: cost). However, if you are concerned about DB slowness, you probably could just as easily use build(:invoice) and invoice.line_items.build(cost: cost).
Be aware that my use of create(:invoice) and build(:invoice) here is in reference to generic "factories", not a reference to a specific gem. You could simply use Model.create and Model.new in their place. Additionally, the line_items.create and line_items.build are provided by AR and have nothing to do with any factory gems.
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...
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.
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.