How to stub a method in ActiveSupport::TestCase - ruby-on-rails

In RSpec I could stub method like this:
allow(company).to receive(:foo){300}
How can I stub a method with ActiveSupport::TestCase?
I have a test like this.
class CompanyTest < ActiveSupport::TestCase
test 'foobar' do
company = companies(:base)
#company.stubs(:foo).returns(300)
assert_nil(company.calculate_bar)
end
end

Minitest comes with a stub method out of the box, in case you don't wanna use external tools:
require 'minitest/mock'
class CompanyTest < ActiveSupport::TestCase
test 'foobar' do
company = companies(:base)
Company.stub :foo, 300 do
assert_nil(company.calculate_bar)
end
end
end

Minitest has some limited functionality for mocks, but I'd suggest using the mocha gem for these kinds of stubs.
The syntax for Mocha is exactly what you have on the commented out line:
class CompanyTest < ActiveSupport::TestCase
test 'foobar' do
company = companies(:base)
company.stubs(:foo).returns(300)
assert_nil(company.calculate_bar)
end
end

Enhancing #Farrukh answer:
If you want to verify the arguments passed, like allow(company).to receive(:foo).with(some_args).and_return(300),
You can use assert_called_with.
# requiring may not be needed, depending on ActiveSupport version
require "active_support/testing/method_call_assertions.rb"
include ActiveSupport::Testing::MethodCallAssertions # so we can use `assert_called_with`
assert_called_with(company, :foo, some_args, returns: 300) do
assert_nil(company.calculate_bar)
end

Related

How to spy on a method called at class level on ActiveRecord model?

If I have a model
module MyModule
def bar(str)
puts str
end
end
MyModel < ActiveRecord::Base
include MyModule
bar('foo')
end
My spec:
describe MyModel do
before do
described_class.stubs(:bar)
end
it 'calls bar with correct arguments' do
# This does not work because it is called before it gets stubbed
expect(described_class).to have_received(:bar).with('foo')
end
end
How can I spy on MyModule#bar when called from MyModel?
Using rspec-rails 2.99 and mocha 0.13.3
If you call elsewhere MyModel.new.bar, you can write in the test
expect_any_instance_of(MyModel).to receive(:bar)
If you want to use 'spy', you can use:
allow_any_instance_of(MyModel).to receive(:bar)
If you have link to your MyModel instance inside the test, you can rewrite above examples such way:
expect(my_model_instance).to receive(:bar)
or
allow(my_model_instance).to receive(:bar)
You should understand that after including any module into you class, instance of that class will be receiver of the method.

Need help understanding Testing in Ruby/Rails using Minitest and Mocha

I was wondering if someone could give a broad explanation (high level overview) of testing using Minitest and Mocha in Rails, and perhaps when to use which types of testing utilities.
I seem to be getting caught up not knowing when to use mock vs stub, expects vs stubs, and the order of operations when mocking and stubbing and knowing how to "break down" the functionality of code.
For an example (and yes, I understand I'm testing side effects, but in this case, I don't know what else to test):
Class to be tested:
# frozen_string_literal: true
class UploadJob < ApplicationJob
queue_as :upload
def perform(batch_id)
Logger.new.time('upload.time') do
Uploader.new(batch_id).upload_data
end
end
end
Initial test that didn't work:
# frozen_string_literal: true
require 'test_helper'
class TestUploadJob < ActiveSupport::TestCase
test 'UploadJob > perform > it should start uploading' do
Logger.stubs(:new).then.stubs(:time).with('upload.time').yields
Uploader.expects(:new).with(1).then.expects(:upload_data)
UploadJob.perform_now(1)
end
End result test that works:
# frozen_string_literal: true
require 'test_helper'
class TestUploadJob < ActiveSupport::TestCase
test 'UploadJob > perform > it should start uploading' do
logger_stub = stub
logger_stub.stubs(:time).with('upload.time').yields
Logger.stubs(:new).returns(logger_stub)
uploader_mock = mock
uploader_mock.expects(:upload_data)
Uploader.expects(:new).with(1).returns(uploader_mock)
UploadJob.perform_now(1)
end
To me, it felt more declarative in the first test to say the exact order things would be called in. In the second test, that works, first I'm stubbing or mocking a method call that comes after the call to <CLASS>.new.
What's a better way of thinking about this/approaching this problem of testing? Are there any good resources specific to testing with Minitest and Mocha, NOT Rspec, that would be suggested reads?
Thanks!

Include module in all MiniTest tests like in RSpec

In RSpec I could create helper modules in /spec/support/...
module MyHelpers
def help1
puts "hi"
end
end
and include it in every spec like this:
RSpec.configure do |config|
config.include(MyHelpers)
end
and use it in my tests like this:
describe User do
it "does something" do
help1
end
end
How can I include a module into all MiniTest tests without repeating myself in every test?
From the Minitest README:
=== How to share code across test classes?
Use a module. That's exactly what they're for:
module UsefulStuff
def useful_method
# ...
end
end
describe Blah do
include UsefulStuff
def test_whatever
# useful_method available here
end
end
Just define the module in a file and use require to pull it in. For example, if 'UsefulStuff' is defined in test/support/useful_stuff.rb, you might have require 'support/useful_stuff' in either your individual test file.
UPDATE:
To clarify, in your existing test/test_helper.rb file or in a new test/test_helper.rb file you create, include the following:
Dir[Rails.root.join("test/support/**/*.rb")].each { |f| require f }
which will require all files in the test/support subdirectory.
Then, in each of your individual test files just add
require 'test_helper'
This is exactly analogous to RSpec, where you have a require 'spec_helper' line at the top of each spec file.
minitest does not provide a way to include or extend a module into every test class in the same way RSpec does.
Your best bet is going to be to re-open the test case class (differs, depending on the minitest version you're using) and include whatever modules you want there. You probably want to do this in either your test_helper or in a dedicated file that lets everyone else know you're monkey-patching minitest. Here are some examples:
For minitest ~> 4 (what you get with the Ruby Standard Library)
module MiniTest
class Unit
class TestCase
include MyHelpers
end
end
end
For minitest 5+
module Minitest
class Test
include MyHelperz
end
end
You can then use the included methods in your test:
class MyTest < Minitest::Test # or MiniTest::Unit::TestCase
def test_something
help1
# ...snip...
end
end
Hope this answers your question!
One thing I will do is create my own Test class inheriting from Minitest::Test. This allows me to do any sort of configuration on my base test class and keeping it isolated to my own project1.
# test_helper.rb
include 'helpers/my_useful_module'
module MyGem
class Test < Minitest::Test
include MyUsefulModule
end
end
# my_test.rb
include 'test_helper'
module MyGem
MyTest < Test
end
end
1 This is most likely unneeded, but I like keeping all my gem code isolated.

Using Rails Test Syntax in Ruby

Like most people, I have learned Rails before Ruby and now I would like to get a better knowledge of Ruby.
I am making some scripts and I would like to test them.
I was surprised by the fact that you can't use Rails' test syntax in plain ruby scripts. The following code does not work:
require "test/unit"
class MyTest < Test::Unit::TestCase
test "one plus one should be equal to two" do
assert_equal 1 + 1, 2
end
end
# Error: wrong number of arguments (1 for 2) (ArgumentError)
You have to use this code instead:
require "test/unit"
class MyTest < Test::Unit::TestCase
def one_plus_one_should_be_equal_to_two
assert_equal 1 + 1, 2
end
end
Which seems less readable to me.
Is it possible to use the "declarative" syntax in plain Ruby scripts?
According to Rails' APIs, the test method is defined inside ActiveSupport::Testing::Declarative module and uses a sort of metaprogramming to add new test methods.
If you don't already have the Rails gem installed, you can just install the activesupport gem:
gem install activesupport
Now you just have to require it and make your class inherit from ActiveSupport::TestCase.
Here is the complete code:
require "test/unit"
require "active_support"
class MyTest < ActiveSupport::TestCase
test "one plus one should be equal to two" do
assert_equal 1 + 1, 2
end
end

Re-using unit tests for models using STI

I have a number of models that use STI and I would like to use the same unit test to test each model. For example, I have:
class RegularList < List
class OtherList < List
class ListTest < ActiveSupport::TestCase
fixtures :lists
def test_word_count
list = lists(:regular_list)
assert_equal(0, list.count)
end
end
How would I go about using the test_word_count test for the OtherList model. The test is much longer so I would rather not have to retype it for each model. Thanks.
EDIT: I am trying to use a mixin as per Randy's suggestion. This is what I have but am getting the error: "Object is not missing constant ListTestMethods! (ArgumentError)":
in lib/list_test_methods.rb:
module ListTestMethods
fixtures :lists
def test_word_count
...
end
end
in regular_list_test.rb:
require File.dirname(__FILE__) + '/../test_helper'
class RegularListTest < ActiveSupport::TestCase
include ListTestMethods
protected
def list_type
return :regular_list
end
end
EDIT: Everything seems to work if I put the fixtures call in the RegularListTest and remove it from the module.
I actually had a similar problem and used a mixin to solve it.
module ListTestMethods
def test_word_count
# the list type method is implemented by the including class
list = lists(list_type)
assert_equal(0, list.count)
end
end
class RegularListTest < ActiveSupport::TestCase
fixtures :lists
include ::ListTestMethods
# Put any regular list specific tests here
protected
def list_type
return :regular_list
end
end
class OtherListTest < ActiveSupport::TestCase
fixtures :lists
include ::ListTestMethods
# Put any other list specific tests here
protected
def list_type
return :other_list
end
end
What works well here is that OtherListTest and RegularListTest are able to grow independently of each other.
Potentially, you could also do this with a base class but since Ruby does not support abstract base classes it isn't as clean of a solution.

Resources