You’ve started a new project and it’s time for your code to depend on a third-party service. It could be something like ElasticSearch, Resque, a billing provider, or just an arbitrary HTTP API. You’re a good developer, so you want this code to be well tested. But how do you test code that fires off requests to a service that’s totally out of your control?
You could skip the tests, but you’ll soon be piling more code on a shaky foundation. Untested code tends to attract more complex code to it, and you’ll eventually feel like the code is too dangerous to refactor because you don’t have the test coverage you need to feel safe. You want to build a stable foundation for your future work, but instead you’ve ended up with an unmaintainable mess.
Avoiding this situation is a lot easier than it seems! With a few tools and a little up-front effort, you can decouple your tests from the services your code depends on, write simpler code, and have the confidence to improve the code you’ve written—without introducing bugs. Instead of procrastinating because you don’t know how to approach writing those next tests, you could look at an interaction between your code and the outside world, and know exactly how to jump right into the middle of it.
Mocha: the quick-and-dirty approach
Mocha is the easiest way to get in between your code and the outside world.
As an example, say you have a
Cart object that triggers a credit
card charge when it gets checked out. You want to make sure that the
cart has an error message attached to it if the charge fails.
You probably won’t want your tests to actually hit the billing system every time the tests run. Even if you did, it might be hard to force that service to return a failure. Here’s what it would look like with Mocha:
1 2 3 4 5 6
Mocha can also fail your tests if methods aren’t called the way you expect them to:
1 2 3 4 5 6
Mocha is simple to use, but can be incredibly handy. You have to be
careful that you’re mocking out only the behavior you don’t want
to happen — it’s easy to mock too much and hide real bugs. You also
don’t want to go overboard with this approach: tests full of
stubs are hard to read and think about.
Test fakes: my preferred approach
If you mock or stub the same methods on the same objects all the time, you can promote your mocks to full-fledged objects (sometimes called test fakes), like this:
1 2 3 4 5 6 7
Fakes are great:
Your fake can keep track of its internal state
The fake can have custom assertion messages and helper functions that make writing your tests easier, like the
total_chargesmethod in the example above.
As a full-fledged object, you get extra editor and language support
If you’re using an editor that supports it, you can get autocomplete, inline documentation, and other things you won’t get by stubbing out individual methods with Mocha. You’ll also get better validations, exception handling, and whatever else you want to build into your fake.
If you use a fake in development mode, you don’t have to have a connection to the real service
You can write your app on the bus, you don’t have to have a forest of services running down your laptop’s battery, and you can set these fake services up to return the data you need to work through edge cases without needing a lot of setup.
These objects can be used outside of your tests
This is probably my favorite part of fakes. You can have a logging client log to both a 3rd party service and your fake, backed by an in-memory array. You could then dump the contents of this array in an admin view on your site, making it much easier to verify that you’re logging what you think you’re logging.
You could do something like this:
1 2 3 4
Writing a fake takes more effort than stubbing out individual methods, but with practice it shouldn’t take more than an hour or two to get a helpful fake built. If you build one that would be useful to other people, share it! I built resque-unit a long time ago, and lots of people still use it today.
How do I get these objects injected, anyway?
You’ll have to get your objects under test to talk to these fakes somehow. Luckily, Ruby is so easy to abuse that injecting fakes usually isn’t hard.
If you control the API of the object under test, it’s best to add a default parameter, an attribute, or a constructor option where you can set your fake:
1 2 3 4 5 6
This is clean when you are talking to the real service and gives you a hook to add flexibility later.
If you don’t control the object or don’t want to add the extra parameter, you can always monkey patch:
1 2 3 4 5 6
It’s uglier in test, but cleaner in environments that don’t use the fake.
Start building your own fake right now
Building fakes gets easier with practice, so you should give it a try now:
- Find a test that talks to an external service. Tests that would fail if you disconnected from the internet are good candidates.
- Figure out what object actually does the communication, and what calls your code makes to that object.
- Create a mostly empty duplicate of that object’s class, and have it log the calls you make to an array.
- Add a method to your fake to return the list of calls made.
- Swap out the real object with your new fake object, and write some assertions against the calls your code makes.
If you give it a try, let me know how it goes!
With these techniques, it won’t be long until you’re able to tame the craziest interactions between your application and the outside world. A simple stub in the right place will let you ship your well-tested code with confidence.
Don't miss out on my next essay
Sign up below to get my free weekly Ruby column. I'll send you original articles and advice every Friday to help make you a smarter, better Ruby developer.