How to test dependancies that make external calls?

I’ve read Mocks and explicit contracts and I’m aware that mocking/stubbing is generally not “the elixir way”, but I’m still not sure what the “right” way is to test code that had dependancies that make external calls.

Here’s an example scenario:

  • I’ve got a number applications that make calls to an external API to get an address from a latitude/longitude.
  • An HTTP request to http://my.geoiplookup.com/address?lat=52&lng=13 returns an address string
  • Rather than implementing this logic in each application, I create a library (a hex package) that wraps the HTTP calls to the external API, and include this library in my applications.
  • The shared library ‘geo_service’, provides an interface that wraps the the external HTTP calls: e.g.GeoService.address_lookup(%{lat: 52, lng: 13}), which parses the response and returns a string

Let’s say I want to test one of my applications that use this ‘geo_service’ library - for example, one of my applications deals with users profiles, and I want to verify that the user’s address is set correctly.

How do I write tests my code that depends on the hex package, which makes external HTTP calls?

From what I’ve read, I believe I should use something like Bypass, to implement a dummy version of the external API calls, inject this into the library and write tests against these known responses.

However, by doing this, I feel I’m loosing decoupling between my application and library. Rather than simply depending on a library that takes in a lat/long and returns a string, my tests now need to know how the library get’s the address string, override the HTTP calls to return a custom response. I also need to implement this in all the applications that depend on this library.

In ruby I would have simply stubbed a call to GeoService.address_lookup, or used the VCR library. I know there are elixir libraries that would allow me to do something similar, but I’d like to know the best practice for something like this in elixir.

Thanks in advance!

Your GeoService is essentially what the TwitterClient is in the “Mocks and explicit contracts” blogpost. Replace it with an InMemoryGeoService in tests and just return dummy addresses in the format of the module. No need to go down to the http request level in that place. The tests of you GeoServices library itself should cover all the inner workings of your library (possibly with Bypass). Applications depending on it should only test to the interface of your library. Only exception might be some end-to-end tests which really hit the geocoding api.

2 Likes

Thanks @LostKobrakai, that makes sense. I’ve been over-thinking this, trying too hard to avoid mocking the external API calls when all I really need to do is build a mock for the interface.

My favorite is just to build a mock web service that returns the values in the format that I need to test.

Then I can say that configuring the endpoint url is a feature of the library as well. :stuck_out_tongue_winking_eye:

@Azolo my problem with that scenario is that I need to know the inner workings of the library to implement that i.e. I must now that my library is calling a url to get the data.

If I’m depending on a library, I want to give it some params and get a value out - I don’t feel I should depend on how it’s getting my data. My application should be oblivious whether it’s using an API or doing an internal transformation. That’s what I like about creating a mock interface instead of a mock underlying service.

Building a mock web service works really nicely when the library is part of your application, but it it’s a hex package that I’m including then I like keep my app and the library decoupled.

Solid point, and a mock service is not always feasible.

But, there are occasions where it just easier to do even if it’s a Hex package.