Testing execution code with RSpec - ruby

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?

Related

How to test that a method is called

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

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.

How do I test an embedded ruby application?

I have an embedded ruby interpreter running inside a c application, and a ruby class that acts an an interface to the c application. So it looks something like this:
class MyApi
include RealCAPI
def api_method
some_call_to_c_api
end
end
Inside my ruby class that interacts with the api I have something like this. I create an instance of the MyApi class and then call methods on that instance.
class Foo
def initialize
api = MyApi.new
...
...
end
def do_something
bar = api.api_method
...
...
...
final_result #is a function of Foo methods but depends on something from the api
end
end
I would like to test Foo class with something like this:
describe Foo do
it "should do something" do
foo = Foo.new
expect(foo.do_something to eq("something")
end
end
The problem is that none of the calls to the api will work outside of the c application.
How do I test this Foo class?
Do I try to somehow test inside the c application?
Do I write a standalone test only "mock up" of the MyApi class, that mimics what happens in the c application?
I realize that if I mock up the api I can't really test it, but at least I will be able to test the classes that use it, right?
I think you have to mock the api, as you said. What you are testing here is the ruby code, so you should probably stick to it and test only the ruby code.
You'll end up with something saying : provided my api does something, then my ruby code works exactly as specified. Which is good, and does not rely on wheter your api is working or not.
Of course, you'll probably have to write some test for your api. But keeping both tests separated seems like a good idea to me !
edit
Not sure if that's your question here, but this could easily be done by using something like
it "should do something" do
MyApi.any_instance.stub(:ping).and_return("pong") #so that when foo calls ping, the api returns "pong"
foo = Foo.new
expect....
end

How can I mock super in ruby using rspec?

I am extending an existing library by creating a child class which extends to the library class.
In the child class, I was able to test most of functionality in initialize method, but was not able to mock super call. The child class looks like something like below.
class Child < SomeLibrary
def initialize(arg)
validate_arg(arg)
do_something
super(arg)
end
def validate_arg(arg)
# do the validation
end
def do_something
#setup = true
end
end
How can I write rspec test (with mocha) such that I can mock super call? Note that I am testing functionality of initialize method in the Child class. Do I have to create separate code path which does not call super when it is provided with extra argument?
You can't mock super, and you shouldn't. When you mock something, you are verifying that a particular message is received, and super is not a message -- it's a keyword.
Instead, figure out what behavior of this class will change if the super call is missing, and write an example that exercises and verifies that behavior.
As #myron suggested you probably want to test the behavior happening in super.
But if you really want to do this, you could do:
expect_any_instance_of(A).to receive(:instance_method).and_call_original
Assuming
class B < A
def instance_method
super
end
end
class A
def instance_method
#
end
end
Disclaimer expect_any_instance_of are a mark of weak test (see):
This feature is sometimes useful when working with legacy code, though
in general we discourage its use for a number of reasons:
The rspec-mocks API is designed for individual object instances, but
this feature operates on entire classes of objects. As a result there
are some semantically confusing edge cases. For example, in
expect_any_instance_of(Widget).to receive(:name).twice it isn't clear
whether a specific instance is expected to receive name twice, or if
two receives total are expected. (It's the former.)
Using this feature is often a design smell. It may be that your test is trying to do too much or that the object under test is too
complex.
It is the most complicated feature of rspec-mocks, and has historically received the most bug reports. (None of the core team
actively use it, which doesn't help.)
A good way to test this is to set an expectation of some action taken by the superclass - example :
class Some::Thing < Some
def instance_method
super
end
end
and the super class:
class Some
def instance_method
another_method
end
def self.another_method # not private!
'does a thing'
end
end
now test :
describe '#instance_method' do
it 'appropriately triggers the super class method' do
sawm = Some::Thing.new
expect(sawm).to receive(:another_method)
sawm.instance_method
end
end
All This Determines Is That Super Was Called On the Superclass
This pattern's usefulness is dependent on how you structure your tests/what expectations you have of the child/derivative class' mutation by way of the super method being applied.
Also - pay close attention to class and instance methods, you will need to adjust allows and expects accordingly
YMMV
A bit late to this party, but what you can also do is forego using the super keyword and instead do
class Parent
def m(*args)
end
end
class Child < Parent
alias super_m m
def m(*args)
super_m(*args)
end
end
That way your super method is accessible like any other method and can e.g. be stubbed like any other method. The main downside is that you have to explicitly pass arguments to the call to the super method.

How do I bypass a call to super method in test

I have a basic structure like this
class Automobile
def some_method
# this code sets up structure for child classes... I want to test this
end
end
class Car < Automobile
def some_method
super
# code specific to Car... it's tested elsewhere so I don't want to test this now
end
end
class CompactCar < Car
def some_method
super
# code specific to CompactCar... I want to test this
end
end
What is the recommended way to test CompactCar and Automobile without running the code from Car? Automobile#some_method provides the structure that is required by child classes, so I want to always test that, but Car's functionality is tested elsewhere and I don't want to duplicate efforts.
One solution is to use class_eval to overwrite Car#some_method, but this isn't ideal because the overwritten method stays in place for the duration of my testing (unless I re-load the original library file with setup/teardown methods... kind of an ugly solution). Also, simply stubbing the call to Car#some_method does not seem to work.
Is there a cleaner/more generally accepted way of doing this?
Just put the specific code into a separate method. You don't appear to be using anything from super. Unless you are?
class CompactCar < Car
def some_method
super
compact_car_specific_code
end
# Test this method in isolation.
def compact_car_specific_code
# code specific to CompactCar... I want to test this
end
end

Resources