Rspec for complicated case - ruby-on-rails

I'm new to RSpec, and struggling with how to test with mock.
This is basically called when webhook comes in.
class InvoiceCreated
def call(event)
invoice = event.data.object
# NOTE: Skip if the invoice is closed.
if invoice.closed == false
stripe_customer = invoice.customer
payment_account = PaymentCardAccount.find_by(stripe_customer_id: stripe_customer)
card_invoice = Invoice.find_card_invoice_in_this_month_within(payment_account: payment_account)
card_invoice.process_invoice_items(stripe_customer: stripe_customer,
event_invoice_id: invoice.id)
card_invoice.process!(:pending, id: invoice.id)
end
end
end
I'd love to use mock and prevent API calling for testing for two lines of code below.
card_invoice.process_invoice_items(stripe_customer: stripe_customer,
event_invoice_id: invoice.id)
card_invoice.process!(:pending, id: invoice.id)
how can I use mock for those?

You can use expect_any_instance_of to check that you call Invoice#process_invoice_items and Invoice#process with the correct parameters. If you do not care about how they are called you can just stub them and use allow_any_instance_of for this.
expect_any_instance_of(Invoice).to receive(:process_invoice_items)
expect_any_instance_of(Invoice).to receive(:process!)
See here for more detailed examples and got through the basic section of rspec-mock to see what you can do with it actually.

Related

How to mock a chain of methods in Minitest

It looks like the only source of information for stubbing a chain of methods properly are 10+ years ago:
https://www.viget.com/articles/stubbing-method-chains-with-mocha/
RoR: Chained Stub using Mocha
I feel pretty frustrated that I can't find information of how to do this properly. I want to basically mock Rails.logger.error.
UPDATE: I basically want to do something as simple as
def my_action
Rails.logger.error "My Error"
render json: { success: true }
end
And want to write a test like this:
it 'should call Rails.logger.error' do
post my_action_url
???
end
I think maybe you misunderstood the term chain of methods in this case, they imply the chain of ActiveRecord::Relation those be able to append another. In your case Rails.logger is a ActiveSupport::Logger and that's it. You can mock the logger and test your case like this:
mock = Minitest::Mock.new
mock.expect :error, nil, ["My Error"] # expect method error be called
Rails.stub :logger, mock do
post my_action_url
end
mock.verify
Beside that, I personally don't like the way they test by stub chain of method, it's so detail, for example: i have a test case for query top ten users and i write a stub for chain of methods such as User.where().order()..., it's ok at first, then suppose i need to refactor the query or create a database view top users for optimize performance purpose, as you see, i need to stub the chain of methods again. Why do we just treat the test case as black box, in my case, the test case should not know (and don't care) how i implement the query, it only check that the result should be the top ten users.
update
Since each request Rails internal call Logger.info then if you want ignore it, you could stub that function:
def mock.info; end
In case you want to test the number of method called or validate the error messages, you can use a spy (i think you already know those unit test concepts)
mock = Minitest::Mock.new
def mock.info; end
spy = Hash.new { |hash, key| hash[key] = [] }
mock.expect(:error, nil) do |error|
spy[:error] << error
end
Rails.stub :logger, mock do
post my_action_url
end
assert spy[:error].size == 1 # call time
assert spy[:error] == ["My Error"] # error messages
It's better to create a test helper method to reuse above code. You can improve that helper method behave like the mockImplementation in Jest if you want.

Stubbing out ActiveRecord models in Service tests

I'm following a TDD approach to building our app, and creating a whole bunch of service objects, keeping models strictly for data management.
Many of the services I've built interface with models. Take for example MakePrintsForRunner:
class MakePrintsForRunner
def initialize(runner)
#runner = runner
end
def from_run_report(run_report)
run_report.photos.each do |photo|
Print.create(photo: photo, subject: #runner)
end
end
end
I appreciate the create method could arguably be abstracted into the Print model, but let's keep it as is for now.
Now, in the spec for MakePrintsForRunner I'm keen to avoid including spec_helper, since I want my service specs to be super fast.
Instead, I stub out the Print class like this:
describe RunnerPhotos do
let(:runner) { double }
let(:photo_1) { double(id: 1) }
let(:photo_2) { double(id: 2) }
let(:run_report) { double(photos: [photo_1, photo_2]) }
before(:each) do
#service = RunnerPhotos.new(runner)
end
describe "#create_print_from_run_report(run_report)" do
before(:each) do
class Print; end
allow(Print).to receive(:create)
#service.create_print_from_run_report(run_report)
end
it "creates a print for every run report photo associating it with the runners" do
expect(Print).to have_received(:create).with(photo: photo_1, subject: runner)
expect(Print).to have_received(:create).with(photo: photo_2, subject: runner)
end
end
end
And all goes green. Perfect!
... Not so fast. When I run the whole test suite, depending on the seed order, I am now running into problems.
It appears that the class Print; end line can sometimes overwrite print.rb's definition of Print (which obviously inherits from ActiveRecord) and therefore fail a bunch of tests at various points in the suite. One example is:
NoMethodError:
undefined method 'reflect_on_association' for Print:Class
This makes for an unhappy suite.
Any advice on how to tackle this. While this is one example, there are numerous times where a service is directly referencing a model's method, and I've taken the above approach to stubbing them out. Is there a better way?
You don't have to create the Print class, simply use the one that is loaded, and stub it:
describe RunnerPhotos do
let(:runner) { double }
let(:photo_1) { double(id: 1) }
let(:photo_2) { double(id: 2) }
let(:run_report) { double(photos: [photo_1, photo_2]) }
before(:each) do
#service = RunnerPhotos.new(runner)
end
describe "#create_print_from_run_report(run_report)" do
before(:each) do
allow(Print).to receive(:create)
#service.create_print_from_run_report(run_report)
end
it "creates a print for every run report photo associating it with the runners" do
expect(Print).to have_received(:create).with(photo: photo_1, subject: runner)
expect(Print).to have_received(:create).with(photo: photo_2, subject: runner)
end
end
end
Edit
If you really need to create the class in the scope of this test alone, you can undefine it at the end of the test (from How to undefine class in Ruby?):
before(:all) do
unless Object.constants.include?(:Print)
class TempPrint; end
Print = TempPrint
end
end
after(:all) do
if Object.constants.include?(:TempPrint)
Object.send(:remove_const, :Print)
end
end
I appreciate the create method could arguably be abstracted into the Print model, but let's keep it as is for now.
Let's see what happens if we ignore this line.
Your difficulty in stubbing a class is a sign that the design is inflexible. Consider passing an already-instantiated object to either the constructor of MakePrintsForRunner or the method #from_run_report. Which to choose depends on the permanence of the object - will the configuration of printing need to change at run time? If not, pass to the constructor, if so, pass to the method.
So for our step 1:
class MakePrintsForRunner
def initialize(runner, printer)
#runner = runner
#printer = printer
end
def from_run_report(run_report)
run_report.photos.each do |photo|
#printer.print(photo: photo, subject: #runner)
end
end
end
Now it's interesting that we're passing two objects to the constructor, yet #runner is only ever passed to the #print method of #printer. This could be a sign that #runner doesn't belong here at all:
class MakePrints
def initialize(printer)
#printer = printer
end
def from_run_report(run_report)
run_report.photos.each do |photo|
#printer.print(photo)
end
end
end
We've simplified MakePrintsForRunner into MakePrints. This only takes a printer at construction time, and a report at method invocation time. The complexity of which runner to use is now the responsibility of the new 'printer' role.
Note that the printer is a role, not necessarily a single class. You can swap the implementation for different printing strategies.
Testing should now be simpler:
photo1 = double('photo')
photo2 = double('photo')
run_report = double('run report', photos: [photo1, photo2])
printer = double('printer')
action = MakePrints.new(printer)
allow(printer).to receive(:print)
action.from_run_report(run_report)
expect(printer).to have_received(:print).with(photo1)
expect(printer).to have_received(:print).with(photo2)
These changes might not suit your domain. Perhaps a runner shouldn't be attached to a printer for more than one print. In this case, perhaps you should take a different next step.
Another future refactoring might be for #from_run_report to become #from_photos, since the report isn't used for anything but gathering photos. At this point the class looks a bit anaemic, and might disappear altogether (eaching over photos and calling #print isn't too interesting).
Now, how to test a printer? Integrate with ActiveRecord. This is your adapter to the outside world, and as such should be integration tested. If all it really does is create a record, I probably wouldn't even bother testing it - it's just a wrapper around an ActiveRecord call.
Class names are just constants so you could use stub_const to stub an undefined constant and return a double.
So instead of defining a class in your before(:each) block do this:
before(:each) do
stub_const('Print', double(create: nil))
#service.create_print_from_run_report(run_report)
end

Mocks and Stubs. I don't get the basics

I am in the process of freeing myself from FactoryGirl (at least in the lib folder). So, I start writing strange stuff like "mock" and "stub". Can somebody help a novice out?
I have this module
module LogWorker
extend self
def check_todo_on_log(log, done)
if done == "1"
log.todo.completed = true
log.todo.save!
elsif done.nil?
log.todo.completed = false
log.todo.save!
end
end
end
log and todo are rails models with a todo :has_many logs association. But that should really not matter when working with stubs and mocks, right?
I have tried many things, but when I pass the mock to the method nothing happens,
describe LogWorker do
it 'should check_todo_on_log'do
todo = mock("todo")
log = mock("log")
log.stub!(:todo).and_return(todo)
todo.stub!(:completed).and_return(false)
LogWorker.check_todo_on_log(log,1)
log.todo.completed.should eq true
end
end
Failures:
1) LogWorker should check_todo_on_log
Failure/Error: log.todo.completed.should eq true
expected: true
got: false
(compared using ==
I would really like to see some spec that would test the LogWorker.check_todo_on_log method with stubs and/or mocks.
Firstly, your check_todo_on_log method is pretty bad. Never, ever use strings as options, especially when the string is "1". Also, if you pass "2", nothing happens. I'll assume though it is just a partial method, and your code isn't really like that :P
Looking at your code, you have three main problems. Firstly, you call LogWorker.check_todo_on_log(log,1). This won't do anything, as your method only does stuff when the second param is the string "1" or nil. Secondly, you stub todo.completed so it always returns false: todo.stub!(:completed).and_return(false). You then test if it is true. Obviously this is going to fail. Finally, you don't mock the save! method. I don't know how the code is actually running for you (it doesn't work for me).
Below is how I would write your specs (note that they are testing weird behaviour as the check_todo_on_log method is also strange).
Firstly, there is an easier way to add mock methods to a mock object. You can pass keys and values to the mock methods, and they will automatically be created.
Next, I put the mocks into let blocks. This allows them to be recreated easily for each test. Finally, I add a test for each possible behaviour of the function.
# you won't need these two lines, they just let the code be run by itself
# without a rails app behind it. This is one of the powers of mocks,
# the Todo and Log classes aren't even defined anywhere, yet I can
# still test the `LogWorker` class!
require 'rspec'
require 'rspec/mocks/standalone'
module LogWorker
extend self
def check_todo_on_log(log, done)
if done == "1"
log.todo.completed = true
log.todo.save!
elsif done.nil?
log.todo.completed = false
log.todo.save!
end
end
end
describe LogWorker do
let(:todo) { mock("Todo", save!: true) }
let(:log) { mock("Log", todo: todo) }
describe :check_todo_on_log do
it 'checks todo when done is "1"'do
todo.should_receive(:completed=).with(true)
LogWorker.check_todo_on_log(log,"1")
end
it 'unchecks todo when done is nil'do
todo.should_receive(:completed=).with(false)
LogWorker.check_todo_on_log(log,nil)
end
it "doesn't do anything when done is not '1' or nil" do
todo.should_not_receive(:completed=)
LogWorker.check_todo_on_log(log,3)
end
end
end
Notice how I am using behaviour based testing? I'm not testing that an attribute on the mock has a value, I am checking that an appropriate methods are called on it. This is the key to correctly using mocks.

How to use mocks correctly?

I have this class:
class EnablePost
def initialize(post_klass, id)
raise "oops" if post_klass.blank?
#post_klass = post_klass
#id = id
end
def perform
post = #post_klass.find_by_id(#id)
return unless post
post.update_attribute :enabled, true
end
end
The spec I have to write to test the above:
describe EnablePost do
it "should enable a post" do
post = mock
post.should_receive(:blank?).and_return(false)
post.should_receive(:find_by_id).with(22).and_return(post)
post.should_receive(:update_attribute).with(:enabled, true)
result = EnablePost.new(Post, 22).perform
result.should be_true
end
end
But what I really want to do is treat EnablePost as a black box. I don't want to have to mock :blank?, :find_by_id or :update_attribute.
That is to say I want my spec to look like:
describe EnablePost do
it "should enable a post" do
post = mock
result = EnablePost.new(post, 22).perform
result.should be_true
end
end
What am I missing here? Am I using mocks incorrectly?
Yes, you're confusing mocks and stubs.
A good mock explanation: http://jamesmead.org/talks/2007-07-09-introduction-to-mock-objects-in-ruby-at-lrug/
Mocks:
Different things to different people
Ambiguous terminology
Confusion with Rails “mocks”
Mock Object:
Expected method invocations set in advance
Verifies actual invocations match expected ones
Also check out http://martinfowler.com/articles/mocksArentStubs.html [thanks to user Zombies in the comments]
If you're using RSpec, it aliases double, mock, and stub. RSpec expects you to choose whichever method name makes your code clearest.
Your first chunk of test code is using the word "mock" correctly. You're setting up the method invocations that you expect to be called, in advance, and then performing them.
However, you're testing two different areas of your code: the first area is the initialize method, the second is the #perform method.
You may find it easier to mock and stub if you write smaller methods:
# What you want to test here is the raise and the member variables.
# You will stub the post_klass.
def initialize(post_klass, post_id) # post_id is a better name
raise "oops" if post_klass.blank?
#post_klass = post_klass
#post_id = post_id # because we don't want to mask Object#id
end
attr_accessor :post_id
attr_accessor :post_klass
# What you want to test here is the post_klass calls #find_by_id with post_id.
# See we've changed from using instance variables to methods.
def post
post_klass.find_by_id(post_id)
end
# What you want to test here is if the update happens.
# To test this, stub the #post method.
def perform
p = post
return unless p
p.update_attribute :enabled, true
end
When you write your code this way, you make it easy to stub the #post method.
See this for RSpec example source code showing the difference between mock and stub:
http://blog.firsthand.ca/2011/12/example-using-rspec-double-mock-and.html

How can I test that my before_save callback does the right thing

I have a callback on my ActiveRecord model as shown below:
before_save :sync_to_external_apis
def sync_to_external_apis
[user, assoc_user].each {|cuser|
if cuser.google_refresh
display_user = other_user(cuser.id)
api = Google.new(:user => cuser)
contact = api.sync_user(display_user)
end
}
end
I would like to write an rspec test which tests that calling save! on an instance of this model causes sync_user to be called on a new Google instance when google_refresh is true. How could I do this?
it "should sync to external apis on save!" do
model = Model.new
model.expects(:sync_to_external_apis)
model.save!
end
As an aside, requesting unreliable resources like the internet during the request-response cycle is a bad idea. I would suggest creating a background job instead.
The usual method for testing is to ensure the results are as expected. Since you're using an API in this case that may complicate things. You may find that using mocha to create a mock object you can send API calls would allow you to substitute the Google class with something that works just as well for testing purposes.
A simpler, yet clunkier approach is to have a "test mode" switch:
def sync_to_external_apis
[ user, assoc_user ].each do |cuser|
if (Rails.env.test?)
#synced_users ||= [ ]
#synced_users << cuser
else
# ...
end
end
end
def did_sync_user?(cuser)
#synced_users and #synced_users.include?(cuser)
end
This is a straightforward approach, but it will not validate that your API calls are being made correctly.
Mocha is the way to go. I'm not familiar with rspec, but this is how you would do it in test unit:
def test_google_api_gets_called_for_user_and_accoc_user
user = mock('User') # define a mock object and label it 'User'
accoc_user = mock('AssocUser') # define a mock object and label it 'AssocUser'
# instantiate the model you're testing with the mock objects
model = Model.new(user, assoc_user)
# stub out the other_user method. It will return cuser1 when the mock user is
# passed in and cuser2 when the mock assoc_user is passed in
cuser1 = mock('Cuser1')
cuser2 = mock('Cuser2')
model.expects(:other_user).with(user).returns(cuser1)
model.expects(:other_user).with(assoc_user).returns(cuser2)
# set the expectations on the Google API
api1 - mock('GoogleApiUser1') # define a mock object and lable it 'GoogleApiUser1'
api2 - mock('GoogleApiUser2') # define a mock object and lable it 'GoogleApiUser2'
# call new on Google passing in the mock user and getting a mock Google api object back
Google.expects(:new).with(:user => cuser1).returns(api1)
api1.expects(:sync_user).with(cuser1)
Google.expects(:new).with(:user => cuser2).returns(api2)
api2.expects(:sync_user).with(cuser2)
# now execute the code which should satisfy all the expectations above
model.save!
end
The above may seem complicated, but it's not once you get the hang of it. You're testing that when you call save, your model does what it is supposed to do, but you don't have the hassle, or time expense of really talking to APIs, instantiating database records, etc.

Resources