No Network Please, I’m Testing
It’s good practice for your automated testing not to require a network connection to an external service to run. At the very least, using an external service is going to slow down your tests (and it could have other consequences).
One solution is to stub out or mock calls to external services, but it’s easy to forget to do that. For instance, let’s say we using HTTParty to POST data to an external website.
class FooClient
def self.send_data(value)
HTTParty.post(url, body: { value: value })
end
end
And here’s a test for it (using Minitest Spec and Mocha but the principle applies to all testing frameworks).
describe FooClient do
before do
HTTParty.expects(:post).once.returns(16)
end it "sends data" do
FooClient.send_data(4).must_equal 16
end
end
But what if you forget to stub out the post
? As your code gets more complicated it’s not always so obvious that an external call is being made.
Enter the NoNetwork
module:
# test/test_helper.rbrequire 'support/no_network'# test/support/no_network.rbmodule NoNetwork
def before_setup
HTTParty.expects(:post).at_least(0).throws("DON'T USE THE NETWORK IN TEST. Please stub HTTParty.post")
end
endclass Minitest::Test
include NoNetwork
end
Now, if you forget to stub out HTTParty the earlier stub in the NoNetwork
module will throw an error telling you about it.
Extending it to other services
You could add code to do the same thing for other services. Perhaps you’re using Stripe and want to isolate that too. Perhaps another part of your code uses Net::HTTP
. You’ll probably want to stub out other methods on HTTParty. Let’s DRY this up and make it easier to add more checks:
module NoNetwork def before_setup
block_class_methods(
Stripe::Customer => [:create],
Stripe::Charge => [:create],
Stripe::Refund => [:create],
HTTParty => [:get, :put, :patch, :post, :delete]
)
block_instance_methods(
Net::HTTP => [:start, :request_get, :request_head, :request_post]
)
end def expect_and_throw(obj, method, description)
obj.
expects(method).
at_least(0).
throws("DON'T USE THE NETWORK IN TEST. Please stub #{description}")
end def block_class_methods(list)
list.each do |obj, methods|
Array(methods).each do |method|
expect_and_throw obj, method, "#{obj}.#{method}"
end
end
end def block_instance_methods(list)
list.each do |obj, methods|
Array(methods).each do |method|
expect_and_throw obj.any_instance, method, "#{obj}##{method}"
end
end
end
endclass Minitest::Test
include NoNetwork
end
Every time you add a new external service then you add it to this module and your whole test suite will then tell you if you’ve omitted to stub out a call.
And it’s a good idea to occasionally run your tests without being connected to the Internet just to check you’ve listed everything you use in the NoNetwork module.
You can see the code for this article here with each step as a separate commit.