Recently when working on a Phoenix project I found that I wanted to avoid making 3rd party API calls when running my tests. Using Twilio, through the ExTwilio library, I was sending SMS messages to users. Each SMS message cost money to send and my tests were generating random phone numbers and messages, so you can see why I needed to do this. I had this method making the 3rd party API call:
To avoid the library call I did some digging in the Plug.Conn docs and saw that when running tests, the adapter field for conn is ‘Plug.Adapters.Test.Conn’. Knowing this I thought pattern matching was a simple way to avoid the API calls during tests, I would just have to pass in conn to my method and edit it like this:
If running tests, the first implementation will be called because it will match first, otherwise the real implementation will be called. With this I am able to avoid my API calls pretty easily, this didn’t seem bad to me as it was only happening in one place in my code.
However with this I am passing in a argument to the function without really doing anything with it inside, so if I had to do this in multiple places it could start to feel dirty. Also, it is dependent on conn, so if I had to call this method anytime a web request was not being processed there would be a issue. To get around this there is another approach that uses the config setup for the project and has a fake module to be called during tests.
Lets say I am are running the same text message send code except it is done in the background and we have no conn, I pull a list of phone numbers and send each a text message every hour. Lets define a module Twilio.ex like this to handle the API calls:
At the same time I would define a fake module that mimics the real module to be used in test cases, FakeTwilio.ex:
Now I can easily use the correct module depending on if I am running tests or not by setting up definitions in our config.exs and test.exs files:
This sets it up so the correct module is retrieved through ‘Application.get_env’ calls, just need to wrap the module in a helper function that will get the right config setup like this:
Automatically here the private text_module method will load up the fake module during tests and the real module otherwise.
Both of these methods allowed me to avoid making 3rd party API calls when running my tests, here is a example project with both of these setups: https://github.com/chiragtoor/test_controlled_apis