I just re-read Practical Object Oriented Programming in Ruby by Sandi Metz, especially the chapter on testing. Also a very useful talk that I recommend Rubyists watch: http://www.youtube.com/watch?v=URSWYvyc42M
She says to test these cases:
Incoming query messages: Test them by asserting what they return.
Incoming command messages: Test the direct public side effects (I have a question about this)
Query messages sent to self: Don't test them
Command messages sent to self: Don't test them
Outgoing query messages: Don't test them
Outgoing command messages: Test that they are sent
For #2, she provided an example similar to this:
#class
class Gear
attr_reader :cog
def set_cog(cog)
#cog = cog
end
end
# example spec
it "sets the value of #cog" do
gear = Gear.new
gear.set_cog(1)
expect(gear.cog).to eq(1)
end
So this is simple because it just sets the value of the instance variable so the side effects are obvious. But what if my method calls another command message? For example:
class Gear
attr_reader :cog, :foo, :bar
def set_cog(cog)
reset_other_attributes
#cog = cog
end
def reset_other_attributes
#foo = nil
#bar = nil
end
end
How should I test that? I'm thinking that it should be treated like an outgoing command message, where you should assert that that message is sent and have a separate test for the reset_other_attributes method.
it "calls the reset_other_attributes method" do
gear = Gear.new
gear.should_receive(:reset_other_attributes)
gear.set_cog(1)
end
Is this correct?
The real reason why this method is hard to test is the fact that it violates the SRP principle. It is setting more than the value of cog.
Anyway, in this case I would test that the expected changes take effect, it doesn't seem reasonable to test that the "reset_other_attributes" method is called. From this snipped, it looks like "reset_other_attributes" shouldn't even be part of the public API.
Setting methods generally should have no hidden side effects.
That is, if given method represent PROCESS, and its understood that that process involve A, B, and C its OK to make single gobled function that mix and match.
And You MUST test all the results (eg. that A was set, B was executed, and C was logged).
But it may be that function actually do two unrelated things. That's bad. Users may forget about this or that side effect and nasty bug just started its life.
Then do write test, like for normal function. Then refactor into two distinct functions.
Related
I have an input method, that intended to read circle radius from console. If input is invalid, method outputs error message and loops to read input again.
So I need to make an rspec test that iterates by array of invalid inputs and expect that
input method will output error to console message each time.
Here is my input class:
# frozen_string_literal: true
require_relative '../data/messages'
# Input class is responsible for reading and writing to and from console and
# querying user
class Input
def read
loop do
print "#{RADIUS_QUERY_MSG}\n> "
radius = gets.strip
return radius.to_f if valid?(radius)
puts INVALID_MSG
end
end
private
def valid?(radius)
/\A[+]?\d+(\.\d+)?\z/.match(radius)
end
end
I've tried this in my rspec test, but it seems to get into some infinite loop:
# frozen_string_literal: true
require 'input'
require_relative '../data/messages'
require_relative '../data/rspec'
RSpec.describe Input do
let(:input) { described_class.new }
describe '#read' do
INVALID_INPUTS.each do |invalid_input|
context "with invalid input \"#{invalid_input}\"" do
it 'tells user that input is invalid' do
allow(input).to receive(:gets).and_return(invalid_input)
expect(input.read).to output("#{INVALID_MSG}\n").to_stdout
end
end
end
end
end
How can I do this properly? Would appreciate any help.
P.S.
Found this article, but it was no use for me. Maybe it will help. https://haughtcodeworks.com/blog/software-development/easy-loop-testing/
P.P.S.
INVALID_MSG and RADIUS_QUERY_MSG are strings and INVALID_INPUTS is an array of strings.
Refactor to Inject Your Test Inputs into the "Real" Method
This is a common problem for code that isn't written test-first. There are a couple of ways to solve it, but the simplest option without mocking, stubbing, or otherwise invalidating your "real" code is simply to refactor the method itself. For example:
def read test_input: nil
loop do
print "#{RADIUS_QUERY_MSG}\n> "
radius = (test_input || gets).strip
return radius.to_f if valid?(radius)
puts INVALID_MSG
end
end
Now you can simply inject whatever values you want into the optional test_input keyword argument from your RSpec tests to ensure that the input is stripped properly and exhibits whatever other behavior you're testing for.
This avoids all sorts of problems you might experience by trying to write around a difficult-to-test method. Either you provide test input directly to the method, in which case the method uses that, or you don't, in which case it calls #gets just as it normally would.
Remember, the goal isn't to test core methods like #gets. Instead, you should be testing the behavior of your method or object given a particular input or state. If you make your methods testable by allowing dependency injection in your code, or refactoring your class to allow modifying instance variables in your test setup and using those rather than method arguments passed to your methods, you ensure that you are testing your real class or method rather than hacking your way around it.
There are certainly other, more complex ways to do what I did above, but they don't seem warranted for this specific example. The KISS principle definitely applies!
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
Why would you use stub given that you should be testing real classes anyways?
I know there are cases that you don't want to test other classes and not go through its other associated class methods but I still think it's better to use real methods.
I can only really see a benefit when you want to quickly skip one method's other associated tasks and return the end result to be used in tests
Are there other benefits though that we should be considering?
(In addition to above I also think stub is risky aswell since your code can change as it evolves and may generate different output to what it is generating in the tests)
It depends on the test that you are performing. For unit tests, where you are only testing a single class, stubs are beneficial.
As an example, assume you are testing a class which sends an email when some other object finishes the did_it! operation:
describe Emailer do
context ".send_email" do
it "sends an email if another object 'did_it!'" do
obj = Obj.new
Emailer.send_email(obj).should == true # the email sends successfully
end
end
end
In this case, if obj.did_it! is a super expensive operation, or it could fail intermittently, this test could have issues.
However, in this test we only care that Emailer.send_email runs correctly when obj.did_it! returns true--we do not care that the obj.did_it! method works, because that is not what we are testing.
So, we use stubs to say, "assuming that obj.did_it! succeeds, does Emailer.send_email actually send email?":
describe Emailer do
context ".send_email" do
it "sends an email if another object 'did_it!'" do
obj = stub(:did_it! => true)
Emailer.send_email(obj).should == true # the email sends successfully
end
end
end
Say I have an object with a method that accesses an object:
def foo
#foo
end
I know I can use send to access that method:
obj.send("foo") # Returns #foo
Is there a straightforward way to do a recursive send to get a parameter on the #foo object, like:
obj.send("foo.bar") # Returns #foo.bar
You can use instance_eval:
obj.instance_eval("foo.bar")
You can even access the instance variable directly:
obj.instance_eval("#foo.bar")
While OP has already accepted an answer using instance_eval(string), I would strongly urge OP to avoid string forms of eval unless absolutely necessary. Eval invokes the ruby compiler -- it's expensive to compute and dangerous to use as it opens a vector for code injection attacks.
As stated there's no need for send at all:
obj.foo.bar
If indeed the names of foo and bar are coming from some non-static calculation, then
obj.send(foo_method).send(bar_method)
is simple and all one needs for this.
If the methods are coming in the form of a dotted string, one can use split and inject to chain the methods:
'foo.bar'.split('.').inject(obj, :send)
Clarifying in response to comments: String eval is one of the riskiest things one can do from a security perspective. If there's any way the string is constructed from user supplied input without incredibly diligent inspection and validation of that input, you should just consider your system owned.
send(method) where method is obtained from user input has risks too, but there's a more limited attack vector. Your user input can cause you to execute any 0-arghument method dispatchable through the receiver. Good practise here would be to always whitelist the methods before dispatching:
VALID_USER_METHODS = %w{foo bar baz}
def safe_send(method)
raise ArgumentError, "#{method} not allowed" unless VALID_USER_METHODS.include?(method.to_s)
send(method)
end
A bit late to the party, but I had to do something similar that had to combine both 'sending' and accessing data from a hash/array in a single call. Basically this allows you to do something like the following
value = obj.send_nested("data.foo['bar'].id")
and under the hood this will do something akin to
obj.send(data).send(foo)['bar'].send(id)
This also works with symbols in the attribute string
value = obj.send_nested('data.foo[:bar][0].id')
which will do something akin to
obj.send(data).send(foo)[:bar][0].send(id)
In the event that you want to use indifferent access you can add that as a parameter as well. E.g.
value = obj.send_nested('data.foo[:bar][0].id', with_indifferent_access: true)
Since it's a bit more involved, here is the link to the gist that you can use to add that method to the base Ruby Object. (It also includes the tests so that you can see how it works)
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.