Skip to content

Instantly share code, notes, and snippets.

@purinkle
Created April 19, 2021 12:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save purinkle/cd2ae4604b2d0973bd20e8fa8d6ebe39 to your computer and use it in GitHub Desktop.
Save purinkle/cd2ae4604b2d0973bd20e8fa8d6ebe39 to your computer and use it in GitHub Desktop.
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
Copy link

Nice tip! 🤩

@iftheshoefritz
Copy link

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
Copy link
Author

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
Copy link

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