How to Wrap and Fake 3rd Party APIs in an Ember Test Suite
We all want automated tests for our applications but some features use APIs that we can’t control in an isolated test environment. Here are a few that might throw you for a loop in an Ember test suite:
- Using window.location = ‘www.example.com’ to send the user to
an external site
- Integrating with a third party search service
- Calling to a payment gateway to charge a credit card
While building a mobile store demo at 201 Created, we wanted to let users make purchases through 3 different methods: Apple Pay, Android Pay, and Stripe’s credit card checkout. To keep our application flexible and our test suite cost-effective, we leaned on the tried-and-true “wrap and fake” approach.
When you find yourself in a similar spot, follow these steps to design enlightenment.
- Identify the Seams
- Imagine the Ideal Interface
- Add the Test
- Fake It!
- Implement the Adapter
Let’s look at an example that uses this approach to make some existing code more testable (and therefore more flexible) for an Apple Pay integration.
Step 1 — Identify the Seams
Here is an apple-pay-button component that makes a payment via Stripe and Apple Pay. Where does the code that we control end and the Stripe and Apple Pay APIs begin?
Scanning through this component I see four places where we call third-party APIs directly.
Step 2 — Imagine the Ideal Interface
The Stripe Apple Pay API is functional but it’s a little jarring in our promise-centric Ember application. Let’s forget about all the fiddly details and see what it would be like to work with the API of our dreams.
Now you might think the ideal interface would do all the work in the promise callbacks for us. Certainly that would be ideal for now, but not when we want to change the data we send to onChargeSuccess or when we need to change the success message. We want an ideal interface that is as thin as possible.
Step 2 — Add the Test
It’s nice to lead with a test when you can, but here we’re adding coverage for existing code. Now that we know how we want to isolate our component from the Stripe and Apple Pay APIs, we can start writing our test. At this point you can throw away the work you did in the previous step for that authentic TDD experience. No seriously. You might want to do that if the complexity of the changes you’re making warrants it.
Although this code could also be tested with an Ember acceptance test, I prefer an integration test here for its speed and focus. After all, we’ll have several error cases to cover before moving on.
We can see that this is a very “integrated” test because we have 3 side-effects
that we’re testing for:
- The correct charge data was sent to Stripe.
- The onChargeSuccess callback was called with the correct attributes.
- We sent a notification to Apple Pay to finalize the transaction.
Notice that even though our test is asynchronous it still follows the Arrange, Act, Assert structure for readability. This is possible because each fake records the information that we later use in assertions.
Step 3 — Fake It
I like to call these testing tools “fakes” which are a more specific subset of “test doubles” as described in this Martin Fowler article. Calling this a “fake” means that it has some minimal implementation for the tests. You may also recognize this pattern from the FakeAuctionServer that evolves throughout Growing Object Oriented Software Guides by Tests. You’ve read that, right? Just like FakeAuctionServer, our fake also has an interface that serves two needs: one for our production code and one for our test code.
Step 4 — Implement the Adapter
Finally we’re ready to implement our ApplePay wrapper as a service, molding the external APIs into the thing we want.
When we look at this service through the lens of the Single Responsibility Principle, we can say that it should only change if our contract with the Stripe API changes.
But how do you test the ApplePay service? Click test it and call it a day.
Or write more tests. Testing is all about trade-offs and if your mission critical app depends on making these payments I would go further. However I would not add more tests to this Ember test suite by stubbing and faking the ApplePaySession and Stripe globals or by intercepting requests at the network layer. Again I’ll refer you to GOOS for a complete discussion but, in short, these would be low value tests. They don’t provide design feedback and they don’t add enough additional confidence that our application is working.
The next level of comfort comes from knowing that your ApplePay adapter service actually works with Stripe. Stripe has a testing API you could use, or maybe it’s worth having part of your test suite make a real charge to Stripe’s production API. However, for modest application needs, a periodic click test whenever you change your adapter (which should be extremely rare) will suffice.
Want to get your test suite under control? Say “Hello” to firstname.lastname@example.org.