JoeZMar

JoeZMar

Testing functions that call 3rd party APIs

I am building an app that uses Stripe for payments. I’ve read about people’s opinion of mocking when it comes to Elixir, but I also know that having actual API calls within your test suite slow your tests down and also increase how many times the API is called if there is a limit. Is testing more there to ensure that changes within your app don’t break when refactoring? Or is it there to help ensure both my app and the API are working together? For example, I could create a mock server that acts as the API in the test environment. The mock server could respond to the same routes as the API, but with a mocked response. If the API ever changes the test won’t break, but if my application makes changes that break the current API implementation it would catch it.

So the question is, how do most people write test for modules that involve other APIs. My other thought was to create two separate test modules. One module that I have a specific @tag meant to skip the module during mix test but it will actually send request using our test secret key for Stripe to ensure proper integration. But still create a mock server that will run every time we run our tests.

Please let me know your opinion.

Most Liked

OvermindDL1

OvermindDL1

Lol, yeah Google isn’t very good about searching for programming stuff, DuckDuckGo is actually a LOT better for searching for programming related terms, it even has whole search modes for it that Google doesn’t have. :slight_smile:

The most basic, direct, and unreadable thing would be from Haskells wiki:
Type witness - HaskellWiki

Even OCaml has an example of them in the GADT section of the official spec: https://caml.inria.fr/pub/docs/manual-ocaml/extn.html#sec256

But in an Elixir world it would be like:

def blah(value, witness), do: witness.(value)

Or in a module form:

def blah(value, witness), do: witness.bloop(value)

In essence a witness is just a type or action that depends on a type.

Typeclasses, like in Haskell, are a non-generic form of witnesses. Like take this function in Haskell:

add :: Num a => a -> a -> a
add l r = l + r

The Num a is a typeclass, if you call this function like add 1 2 it will return 3 and if you call it like add 1.0 2.0 it will return 3.0, for any type that fulfills the typeclass Num 'a. However, look at the =>, that’s just a special operator in Haskell that means to ‘auto-fill what comes before’, let’s turn that back into a ->:

add :: Num a -> a -> a -> a
add w l r = (+) w l r

Now you have to call it like add (Num Int) 1 2 to return 3, that Num Int is the witness, the typeclass is essentially a module reified on that specific type based on the typeclass definition of it, and it passes that module in that location (it’s actually a record in Haskell, but whatever).

In other words, a witness just allows you pass an action over some other type into a function. Haskell’s typeclasses make it “baked in”, in that you can define a witness globally and it can be used globally, but you can’t change it, it is what it is defined as, where if it is something you have to pass in manually, as the OCaml ecosystem does, then you can change it at will, which makes, for example, mocking it absolutely trivial.

Passing in a module or a set of functions into something to operate on a value, whether also passed in or existing entirely internally, but is specified and handled by those functions, makes those functions/module a witness.

In Elixir, I keep my work program very segmented as lots of small dependencies, but they all share the main app’s Repo instead of their own by me defining the MyServer.Repo module in the global config for each dependency, they then access it just via Application.get_env/2 each time they need it (via a default option on function args, but close enough). In other words I am passing in a witness, the Repo, to ‘witness’ or operator over the data that is being processed, I.E. t he schemas and changesets. This is a pattern I use, probably excessively, because of my OCaml history (super common pattern there), and consequently it makes it sooo easy to ‘mock’ things without needing to grab a code generator like Mox or so.

As an addition, I heavily follow the pattern of almost every function taking a set of optional named arguments, like:

def blah_something(a, b, c opts) do
  repo = get_repo(opts)
  # Use stuff like `repo.insert/1`
end

Where get_repo is essentially this defined fairly globally included:

Application.get_env(...) || throw "Repo not set for #{...}"
def get_repo(params, opts \\ []) do
  key = opts[:key] || :repo
  cond do
    is_atom(params[key]) -> params[key] # This actually checks the basic structure of the module
    Application.get_env(...) -> Application.get_env(...)
    # other stuff
    :else ->
      Logger.error "blah"
      raise %NoRepoException{}
  end
end

So I can define a repo globally for a dependency, or I can redefine it on a function-by-function basis, etc… Regardless, it’s just a witness being passed in ‘somehow’ that the functions use to perform work. It’s an exceptionally old pattern in ML languages. :slight_smile:

gregvaughn

gregvaughn

I work on a project that is very much an integration layer between many 3rd party APIs (20-ish including stripe). All of our automated tests go through mocks in some form or fashion. Depending on how “old” a particular integration is we might use the hex package bypass in some cases, but we also have a home-built global mocking framework that prevents async tests and is based upon indirection in the Application.env. Lately we’ve moved to a struct-based command/token approach which I have discussed a bit publicly (GitHub - gvaughn/quest: Pattern for building API clients in Elixir · GitHub).

In any case, we do not have automated tests that actually hit 3rd party APIs. We’re prepared to deal with a temporary production outage if a 3rd party changes their responses significantly. But if that is a high concern of yours, yes, I’d use an ExUnit @tag to manage when it is executed.

Fl4m3Ph03n1x

Fl4m3Ph03n1x

There is no silver bullet. I have a very personal take on testing, some people agree others don’t. What I can say in your case, is that you shouldn’t make your application dependent on Stripe, you should have a connector, a contract, and then have the Stripe client implement that contract. At least, this is the original idea Jose Valim has about testing in Elixir: Mocks and explicit contracts « Plataformatec Blog

Thus, I believe the community would suggest something among the lines of:

  1. Define a contract
  2. Use a mock that obeys the contract in your tests

Me personally, I prefer constructor dependency injection. When I create my entities in Elixir, I simply pass them the set of functions they are going to use (I inject the dependencies). Then when I need to test, I simply pass dummy functions as dependencies, or better, I pass stubs and spies to make sure I am performing the external calls as I am supposed to.

This goes in line with behavioral tessting, some people don’t like it because it couples your test to your implementation a little bit more, but I find this is the best way to ensure every unit works as intended. As usual, YMMV.

Where Next?

Popular in Questions Top

aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lists...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New
RisingFromAshes
I’ve read in another post that it may be possible with a router helper - but I couldn’t find an appropriate one, and tbh, I’m still just ...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers’ Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
JDanielMartinez
Hi! May someone helps me, please! I have two apps into an umbrella project: the first one is Database, which manages queries, and the se...
New
srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call t...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New

Other popular topics Top

siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New
johnnyicon
Hi all, I’ve just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I’m trying to use Postgres...
New
chrismccord
Phoenix 1.4.0 released Phoenix 1.4 is out! This release ships with exciting new features, most notably with HTTP2 support, improved deve...
688 30877 112
New
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 43622 214
New
JakeBecker
TL;DR: I’ve just released an implementation of Microsoft’s IDE-independent Language Server Protocol for Elixir. It adds language support ...
1144 53690 245
New
aesmail
Hello guys, I have finally made it. I created an admin interface for a framework. It’s been on my todo list for years and with the curre...
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
AstonJ
Seen any cool LiveView demos, sample apps or examples? Please post them here! :003:
New

We're in Beta

About us Mission Statement