Optimizing nested RSpec contexts and examples - ruby

I really like the self-documenting nature of RSpec tests so I often create many nested contexts and examples in order to clarify the intent of the tested objects and the situation in which they are being tested, like in this simplified example:
RSpec.describe Foo do
... some let definitions ...
context 'when used properly' do
before do
something_expensive_to_calculate
end
it 'is successful' do
...
end
it 'has benefits' do
....
end
it 'has the power to change the world' do
...
end
end
context 'in evil hands' do
... more nested contexts and examples ...
end
end
Such approach, however, seems to repeat the setup phase for each of the examples and because of that slows the tests down considerably.
The question is whether there are some patterns which allow to add a documentation text to a group of assertions (like I use 'it'), but which would not set up the context as 'it' does.
I found before(:all) blocks. Unfortunately let variables cannot be used in them and so it is not a solution for me. Another solution would be let variables with context lifetimes, but it seems there is no such thing in RSpec.

TestProf has something that you seem to need https://test-prof.evilmartians.io/#/recipes/let_it_be
let_it_be(:foo) { very_expensive_setup }
Which would instantiate the :foo once and keep its state for all the examples.
Make sure you also ready through caveates as this is not just simply better let

I'm unsure if I properly understand your question but perhaps what you are looking for is the subject syntax ?
RSpec.describe Foo do
... some let definitions ...
context 'when used properly' do
subject { do_some stuff }
it 'whatever' do
expect(something).to_not eq true
expect(subject).to eq false
end
end
end
The subject clause allows you to take a series of actions relating to something you are trying to test and re-use the actions inside the subject clause over and over again.

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

How can two specs share the same "it" block?

I have two tests that are very similar. In fact, both tests should produce the same results, but for different inputs. Each needs its own before block but in the interest of DRY I'd like them to share the same it block.
Is that even possible? If so, how?
Shared Examples in Rspec are designed to be used for this purpose. You can keep common it blocks inside a shared example and include them in describe or context blocks.
Simplest example of shared_examples would be,
RSpec.shared_examples "unauthorized_response_examples" do
it { expect(subject).to respond_with(403) }
it { expect(json['message']).to eq(I18n.t("unauthorized")) }
end
And inside your controller specs whenever you need to check unauthorized response you can include examples like,
...
include_examples "unauthorized_response_examples"
Also, you can pass on parameters, action names and controller names and have before(:each|:all) hooks and nested contexts or describe.
For more, you can look at rspec documentation.
Helper methods. (Excuse the horribleness of the example. Would have been better if you'd posted yours :P)
describe "soup" do
def soup_is_salty # helper method! \o/
soup.add(:meat)
soup.add(:egg)
soup.cook
soup.salty?
end
describe "with carrot" do
before(:all) do
soup.add(:carrot)
end
it "should be salty" do
soup_is_salty # get help from helper method! \o/
end
end
describe "soup with potato" do
before(:all) do
soup.add(:potato)
end
it "should be salty" do
soup_is_salty # get help from helper method! \o/
end
end
end
Take the block and create and external method
for example I have some tests that require me to login to my app. So I have a helper.rb file that I include in each spec and that contains a "login" block. Then in each test I can just call login

How can I better test equality for decorated objects?

I'm having trouble with equality matchers in RSpec and Draper decorated objects.
Specs to show what's going on:
context 'how to use the right equality matcher' do
let(:page) { build(:page) }
let(:decorated_page) { page.decorate }
it "should pass, but doesn't" do
expect(decorated_page).to_not eq page
end
it 'proves the classes are different' do
expect(decorated_page).to be_a PageDecorator
expect(page).to be_a Page
expect(decorated_page.class).to_not eq page.class
end
it 'has a work around' do
expect(decorated_page).to be_decorated_with PageDecorator
expect(page).to_not be_decorated_with PageDecorator
end
end
I know RSpec has a few different equality checkers, and that eq is the "weakest" but I would have thought not having the same class would be a deal breaker.
As you can see, I have a work around for this case thanks to Draper's matchers. But I feel like I must be missing something for that test to fail.
Question:
What equality matcher should I use to get the should pass, but doesn't test to pass?
I think that you're encountering two misunderstandings.
You're asking about testing whether the page is decorated or not. You shouldn't test for this. Instead, you should test for the outcome of the decoration, i.e. the behavior. For example if the decorator is supposed to add a new method "foo" then test that the "foo" method works the way you want.
The Draper decorator makes model.decorate == model. This intentionally makes the decoration "invisible" to testing code. For example, expect(decorated_page).to eq page will succeed, because RSpec is using == for the comparison, then Draper is intercepting ==. This is why your spec that says "should pass, but doesn't" is behaving the way it is.
If you really do want to test that decoration happened to the page, try this:
expect(decorated_page.object).to eq page
If you really do want to test that the decorated page is different than the page, try this:
expect(decorated_page.object_id).to_not eq page.object_id

How to pass an external object to rspec tests?

I have a series of RSpec tests - each one living in their own files - that all need a specific object to be ran (shop).
Since setting up the shop object is complicated, I want to have a master file that builds the shop object and then passes it to all the RSpec test files in turn. The idea is that those writing the test scripts do not need to know anything about the setup step.
I could not figure out how to bring something from the outside world into an rspec test case. I have tried variations around the lines of this:
file master.rb:
describe "Testing tests/some_test.rb" do
before :all do
#shop = some_complex_operations
end
load 'tests/some_test.rb'
end
file tests/some_test.rb:
describe 'Some feature' do
it 'should know about #shop'
expect(#shop).not_to be nil
end
end
When I run rspec master.rb then some_test.rb fails.
I know what I outlined is probably a very wrong way to go about this, but I hope at least you get the idea.
How can I implement such a thing cleanly?
What you need is a global hook.
You can create a spec_helper and add there a before all global hook. For example:
RSpec.configure do |config|
config.before(:each) do
#shop = "in case you need a fresh instance of shop each time"
end
config.before(:all) do
#shop = "in case you need a single instance (i.e. you don't modify it)"
end
end
Then you should require this helper from each of your specs. The spec_helper can store any global hooks as well as helpers, or other dependencies for your tests.
When you are writing a test, it should test one thing. shop might be a very complex object, which many objects and methods interact with, but I should guess none of them are aware of the complexity of shop - each method is interested in some aspect of shop.
Therefore I suggest that instead of building a complex object for each test, make a double of it, make it expect what's relevant, and then simply stub the rest:
describe 'Some feature' do
let(:shop) { double('shop').as_nil_object }
let(:my_object) { MyClass.new(shop: shop) }
it 'should do something awesome with shop' do
expect(shop).to receive(:awesome_data).and_return(my_data: 'is_this')
expect(my_object.do_something_awesome).to eq 'how awesome is_this?'
end
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.

Resources