How to test that a method is called - ruby

I have the following:
class Foo
def bar(some_arg)
end
end
It is called as Foo.new.bar(some_arg). How do I test this in rspec? I don't know how to know whether I've created an instance of Foo that has called bar.

receive_message_chain is considered a smell as it makes it easy to violate the Law of Demeter.
expect_any_instance_of is considered a smell in that it is not specific as to which instance of Foo is being called.
As #GavinMiller noted, those practices are generally reserved for legacy code that you do not control.
Here's how to test Foo.new.bar(arg) without either:
class Baz
def do_something
Foo.new.bar('arg')
end
end
describe Baz do
subject(:baz) { described_class.new }
describe '#do_something' do
let(:foo) { instance_double(Foo, bar: true) }
before do
allow(Foo).to receive(:new).and_return(foo)
baz.do_something
end
it 'instantiates a Foo' do
expect(Foo).to have_received(:new).with(no_args)
end
it 'delegates to bar' do
expect(foo).to have_received(:bar).with('arg')
end
end
end
Note: I'm hard coding the arg here for simplicity. But, you could just as easily mock it, too. Showing that here would depend on how the arg is instantiated.
EDIT
It is important to note that these tests are intimately familiar with the underlying implementation. Therefore, if you change the implementation, the tests will fail. How to fix that issue depends on what exactly the Baz#do_something method does.
Let's say Baz#do_something actually just looks up a value from Foo#bar based on the arg and returns it without changing state anywhere. (This is called a Query method.) In that case, our tests should not care about Foo at all, they should only care that the correct value is returned by Baz#do_something.
On the other hand, let's say that Baz#do_something actually does change state somewhere, but does not return a testable value. (This is called a Command method.) In this case, we need to assert that the correct collaborators were called with the correct parameters. But, we can trust that the unit tests for those other objects will actually test their internals, so we can use mocks as placeholders. (The tests I showed above are of this variety.)
There's a fantastic talk on this by Sandi Metz from back in 2013. The specifics of the technologies she mentions have changed. But, the core content of how to test what is 100% relevant today.

Easiest way is to use expect_any_instance_of.
expect_any_instance_of(Foo).to receive(:bar).with(expect_arg).and_return(expected_result)
That said, this method is discouraged since it's complicated, it's a design smell, and it can result in weird behaviour. The suggested usage is for legacy code that you don't have full control over.
Speculating on what your code looks like, I'd expect something like this:
class Baz
def do_stuff
Foo.new.bar(arg)
end
end
it 'tests Baz but have to use expect_any_instance_of' do
expect_any_instance_of(Foo).to receive(:bar).with(expect_arg).and_return(expected_result)
Baz.do_stuff
# ...
end
If this is the situation you find yourself in, you're best off to raise the class instantiation into a default argument like this:
class Baz
def do_stuff(foo_instance = Foo.new)
foo_instance.bar(arg)
end
end
That way you can pass in a mock in place of the default instantiation:
it 'tests Baz properly now' do
mock_foo = stub(Foo)
Baz.do_stuff(mock_foo)
# ...
end
This is known as dependency injection. It's a bit of a forgotten art in Ruby but if you read up about Java testing patterns you'll find it. The rabbit hole goes pretty deep though once you start going that route and tends to be overkill for Ruby.

If you're mocking this methods in another class spec (say BazClass), then the mock method would just return an object with the information you are expecting. For example, if you use Foo#bar in this Baz#some_method spec, you can do this:
# Baz#some_method
def some_method(some_arg)
Foo.new.bar(some_arg)
end
#spec for Baz
it "baz#some_method" do
allow(Foo).to receive_message_chain(:bar).and_return(some_object)
expect(Baz.new.some_method(args)).to eq(something)
end
otherwise if you want the Foo to actually call the method and run it, then you would just call the method regularly
#spec for Baz
it "baz#some_method" do
result = Baz.new.some_method(args)
#foo = Foo.new.bar(args)
expect(result).to eq(#foo)
end
edit:
it "Foo to receive :bar" do
expect(Foo.new).to receive(:bar)
Baz.new.some_method(args)
end

Related

RSpec test method is called on `main` object

Sometimes we call methods on the ruby main objects. For example we call create for FactoryBot and we call _() for I18n.
What's a proper way to test these top level methods got called in RSpec?
For example, I want to test N_ is called, but it would not work because the self in Rspec and self in the file are different.
# spec
describe 'unfound_translations' do
it 'includes dynamic translations' do
expect(self).to receive(:N_)
load '/path/to/unfound_translations.rb')
end
end
# unfound_translations.rb
N_('foo')
However this does not pass.
Ok, I get your problem now. Your main issue is that self in it block is different that self inside unfound_translations.rb. So you're setting expectations on one object and method N_ is called on something completely different.
(Edit: I just realized, when reading the subject of this question again, that you already was aware of it. Sorry for stating the obvious... leaving it so it may be useful to others)
I managed to have a hacky way that is working, here it is:
# missing_translations.rb
N_('foo')
and the spec (I defined a simple module for tests inside it for simplicity):
module N
def N_(what)
puts what
end
end
RSpec.describe 'foo' do
let(:klass) do
Class.new do
extend N
end
end
it do
expect(klass).to receive(:N_)
klass.class_eval do
eval(File.read('missing_translations.rb'))
end
end
end
What it does it's creating an anonymous class that. And evaluating contents of missing_translations.rb inside means that klass is the thing that receives N_ method. So you can set expectations there.
I'm pretty sure you can replace extend N module with whatever module is giving you N_ method and this should work.
It's hacky, but not much effort so maybe good enough until more elegant solution is provided.

RSpec testing of a class which uses a gem object as an instance variable

So I'm pretty new to Rspec and I'm trying to figure out how to write tests for a class that takes an object as a constructor parameter and sets that object to an instance variable. Then it calls that instance variable's object methods in other methods.
Example:
class ClassA
def initialize(string_object, gem_object)
#instance_variable1 = gem_object
#string = string_object
end
def check_validity?(some_arg)
unless #instance_variable1.gemObjectMethod1.gemObjectMethod2(some_arg).empty?
return true
end
false
end
..
..
end
I feel very lost in how to write specifications for this. For one I don't really understand what specifying a constructor actually entails. What I realize is that I'd have to find some way of mocking or stubbing the gem_object I'm getting as argument, but I'm not sure how.
For the next method, what I've tried to this point is:
describe '#check_validity?' do
context 'gets empty list' do
let (:actual) { subject.check_validity?("sample") }
before do
allow(subject).to receive(#instance_variable1.gemObjectMethod1.gemObjectMethod2).with("sample").and_return([])
end
it 'returns false' do
expect(actual).to be false
end
end
end
But this gives me error relating to my constructor saying that it expected 2 arguments but was given 0.
Any help would be much appreciated! Also, I couldn't really find anything on line about specifying constructors with their arguments mocked. Maybe I'm looking in the wrong place or maybe missing something obvious as this is my first experience with BDD.
In RSpec, 'receive' is a method that accepts a symbol that represents the name of a method. (It allows you to chain a 'with' method that accepts the expected list of parameters.) To fix the before-block you could do this:
before do
allow(subject.instance_variable_get(:#instance_variable1).gemObjectMethod1).to receive(:gemObjectMethod2).with("sample").and_return([])
end
The sheer ugliness of that, though, suggests that something is wrong. And it is. The code is violating the law of demeter pretty badly and the test is being drawn into it.
As a first attempt to clean it up, you might consider a method that "caches" the results of calling #instance_variable1.gemObjectMethod1. Let's say that that first method returns an enumerable group of widgets. You could change your class to include something like this:
def check_validity(a_string)
widgets.gemObjectMethod2(a_string).empty?
end
private
def widgets
#widgets ||= #instance_variable1.gemObjectMethod1
end
Your class still knows a bit too much about the gem object, but now you have broken it down in such a way that you could refactor how you find widgets -- perhaps a different gem or your own implementation of it. For the purposes of your testing, you can isolate that decision from the test by mocking widgets.
let(:gem_widgets) do
instance_double(GemObjectMethod1ResultClass, gemObjectMethod2: true)
end
before do
allow(subject).to receive(:widgets).and_return(gem_widgets)
allow(gem_widgets).to receive(:gemObjectMethod2).with("sample").
and_return([])
end
it 'should pass with "sample"' do
expect(actual).to eql true
end

Rspec - How to write specs for a chain of methods

I'm learning rspec, and I'm wondering what the most effective way to write specs for a method that calls a chain of other methods. For example:
class Example1
def foo(dependency)
dependency.bar("A")
dependency.baz("B")
dependency.bzz("C")
end
end
Ideally I would like to write specs like this:
it "should call bar" do
ex = Example1.new
dep = mock
dep.should_receive(:bar).with("A")
ex.foo(dep)
end
it "should call baz"
...
it "should call bzz"
...
When I do that, however, I (understandably) get exceptions like 'unexpected method call baz'.
So what's the best way to deal with that? I have come up with a couple of ideas but I don't know if any of them are good.
Make the mock dependency an "as_null_object" so it ignores the extra calls. (Down side - if I was calling unwanted random stuff on that object, I wouldn't know it)
Stub out the two unused dependency method calls in each spec (Down side - feels very DRY)
Stub out all three dependency calls in a 'before' (Down side - puts a lot of junk in the 'before')
It sounds like you have already worked out which options RSpec gives you. I would go with option 1 and use as_null_object. It's true that you might be missing other random method calls on that object but I would be ok with that if the point of each of these tests was simply to assert that a particular method was being called, especially if I have higher level integration tests covering this method.
If you really need to verify that no other methods are called on dependency then option 3 may make sense but such tests can be brittle when implementation changes.
As an aside, to make your test a little simpler you can use subject to avoid explicitly instantiating Example1 (assuming you are using a describe Example1 block), e.g.:
subject.foo(dep)
(However see discussion in comments - an implicit subject can hide intention).
RSpec has a feature called stub_chain: https://www.relishapp.com/rspec/rspec-mocks/v/2-0/docs/stubs/stub-a-chain-of-methods
What about testing them all in one example?
it "should call bar"
ex = Example1.new
dep = mock
dep.should_receive("bar").with("A")
dep.should_receive("baz").with("B")
dep.should_receive("bzz").with("C")
ex.foo(dep)
end
I believe you can use RSpec to verify the order in which they are called, if that matters.
However, this kind of approach often indicate that there is a problem with how the code is written, e.g. a Law Of Demeter violation. In your example, foo should be a methed on the dependency's class.
I would test this code in this way:
describe "checking foo method" do
before(:each) do
#ex = Example1.new
#test = ClassOfDependency.any_instance
#test.as_null_object
end
after(:each) do
#ex.foo(dependency)
end
it "should call bar method" do
#test.should_receive(:bar).with("A")
end
it "should call baz method" do
#test.should_receive(:baz).with("B")
end
it "should call bzz method" do
#test.should_receive(:bzz).with("C")
end
end
But I'm not sure that it will work, hope it'll give you some ideas.

Testing execution code with RSpec

I have piece of code to test that is not wrapped in a method. It just stands alone with itself in a Ruby class.
begin
# Do stuff - bunch of Ruby code
end
This is not a Rails app. It's a standalone Ruby class. I don't want to execute the whole begin end statement in my rspec tests. How do you test something like this? Should it be done using mocks/stubs? I asked a couple of people but they also didn't know the answer.
I've found that this is easier to test if you can encapsulate the behavior in a method or a module, but it really depends on what code you're trying to execute. If the code winds up altering the class in a public fashion, you can write tests around the fact that the class behaves as expected in memory. For instance:
class Foo
attr_accessor :bar
end
describe Foo
it "should have an attr_accessor bar" do
foo = Foo.new
foo.bar = "baz"
foo.bar.should == "baz"
end
end
This becomes more difficult if you're altering the class in a way that is private.
I've had luck in the past by rewriting this type of behavior into a method that can be explicitly called. It makes testing a lot easier, as well as make it a lot easier to understand timing when troubleshooting problems. For instance:
class Foo
def self.run
# do stuff
end
end
Can you provide a little more context of what you're trying to do in your class?

Is there a way to undo Mocha stubbing of any_instance in Test::Unit

Much like this question, I too am using Ryan Bates's nifty_scaffold. It has the desirable aspect of using Mocha's any_instance method to force an "invalid" state in model objects buried behind the controller.
Unlike the question I linked to, I'm not using RSpec, but Test::Unit. That means that the two RSpec-centric solutions there won't work for me.
Is there a general (ie: works with Test::Unit) way to remove the any_instance stubbing? I believe that it's causing a bug in my tests, and I'd like to verify that.
As it happens, Mocha 0.10.0 allows unstubbing on any_instance().
str = "Not Stubbed!"
String.any_instance.stubs(:to_s).returns("Stubbed!")
puts str.to_s # "Stubbed!"
String.any_instance.unstub(:to_s)
puts str.to_s # "Not Stubbed!"
Mocha does not provide such a functionality. However you can implement it yourself.
The first thing we should know about mocha is that mocha actually replaces the original methods when you stub them. So in order to be able to restore these methods later, you must keep a reference to the former ones. It can be easily achieved by: alias new_method old_method.
It must be done before mocking the old_method.
Now, to unmock a method, you only need to alias old_method new_method.
Consider the following code:
class A
def a
true
end
end
class TestA < Test::Unit::TestCase
def test_undo_mock
a = A.new
A.class_eval {alias unmocked_a a}
A.any_instance.stubs(:a).returns("b")
assert a.a, "b"
A.class_eval {alias a unmocked_a}
assert a.a, "a"
end
end
If you want to remove all your stubs/expectations in one go, then you can do that using mocha_teardown (eg. call self.mocha_teardown).
May be a little bit destructive in this case, however.

Resources