Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
How to use `#tap` to make your stubs more useful

Tap Your Stubs

When writing code that depends on another class, we sometimes like to stub out the dependency. This can result in test examples that are hard to read and full of details that aren't relevant to the actual test.

it "does something" do
  dependency = double(:dependency)
  allow(dependency).to receive(:foo).with(:bar).and_return(:baz)
  allow(Dependency).to receive(:new).and_return(dependency)
  
  SomeClass.new.some_method
  
  expect(dependency).to have_received(:foo).with(:bar)
end

We could increase the readability of this spec by extracting the setup into a method.

it "does something" do
  dependency = stub_dependency
  
  SomeClass.new.some_method
  
  expect(dependency).to have_received(:foo).with(:bar)
end

def stub_dependency
  dependency = double(:dependency)
  allow(dependency).to receive(:foo).with(:bar).and_return(:baz)
  allow(Dependency).to receive(:new).and_return(dependency)
  dependency
end

Now the details of the stubbing that are not specific to the test are moved away to somewhere more helpful. On top of this benefit, we can now share the setup between tests.

The next step would be to make the stubbing more readable by using #tap. #tap allows us to invoke side-effects on an object before returning the original object.

def stub_dependency
  double(:dependency).tap do |dependency|
    allow(dependency).to receive(:foo).with(:bar).and_return(:baz)
    allow(Dependency).to receive(:new).and_return(dependency)
  end
end

Using some well-named methods and #tap we can make our tests a little bit happier.

@gnclmorais

This comment has been minimized.

Copy link

@gnclmorais gnclmorais commented Apr 23, 2021

Nice tip! 🤩

@iftheshoefritz

This comment has been minimized.

Copy link

@iftheshoefritz iftheshoefritz commented Apr 27, 2021

It'd be nice if SomeClass allowed you to inject the instance of Dependency instead of knowing how to create it (and forcing you to write test code like this). When I have to use allow to receive :new I go looking for a way to change how the class I'm testing works

@purinkle

This comment has been minimized.

Copy link
Owner Author

@purinkle purinkle commented Apr 28, 2021

I love a bit of dependency injection.

If it's possible and make sense, I'll look to change the design of my object.

@iftheshoefritz

This comment has been minimized.

Copy link

@iftheshoefritz iftheshoefritz commented Apr 29, 2021

If there's no better place to create the object, a class method that initialises Dependency and passes it to SomeClass.new(dependency_instance) could work. Then I'd just test the initialize with my own double and not worry too much about testing the class method.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment