KristerV

KristerV

Getting the data for Mox expectations for API mocks in a sane manner

My app is making a bunch of API requests to a 3rd party and I’m writing integration tests, thus mocking responses with Mox.

The way I write expectations is that I IO.inspect() every request by hand and copy the data (only needed parts) into the expectations. If I change a test or function i just disable the mock module, print out actual responses, copy-paste response to expectation, enable MockModule again. Takes forever, but is doable. Usually a change means getting one datapoint, not too bad.

So one day the underlying endpoint changes. The tests stay the same. The functions stay the same. But the responses change. Now if I want to keep my tests up-to-date all of the tests need new expectations!! I actually kept the tests unchanged until now, but on another day the signature of most of these functions have changed so I need the new responses across the board…

I’m pulling my hair out at this point.

I must be doing something wrong. There is no way this is how things are done - using debugging tools to fill in the expectations.

I can understand in this situation people may not mock the responses from the API’s, but the already parsed struct-like data instead. But i really want to test the whole processess from start to finish, kind of loses its meaning otherwise.

Is there another way to write expectations? And yeah, they are very specific, i’ve already used stubs as much as i can.

how can i automate this? tempted to just erase the tests and forget TDD (no, not really, but it’s a sad situation).

Most Liked

LostKobrakai

LostKobrakai

Why do your expectations need to change? Naively I‘d say you don‘t want to assert on the response you get from a third party, but you want to assert on what those responses mean to your systems state. Otherwise you‘re testing the third party system not yours.

KristerV

KristerV

okay i’ve thought about this long and hard. i keep feeling that i’m doing something wrong. so i spent days refactoring and simplifying the architecture and i think i got a clear picture now.

problem was that my requests were messy and there was no clear point to differentiate business logic from request stuff. so here’s how i’m thinking about it now:

i think this is ultimately the correct approach regardless how i test the business logic and API requests. i’ve simplified so i do have a single module for most of the requests now (that calls the complex stuff from there). and for a start i’ll just not run the API tests automatically and mock the business logic that happens after the reqests instead.

this is an interesting one. i do in fact want to test that my whole system works even if the 3rd party API changes, but you’re right that i don’t need to test that very often.

another issue here is that before business logic happens the API communication has complexity in itself already. say one function makes 3 requests and puts a single struct together that way. i do want to test this. but again, keeping business and API tests separate here i think is the solution.

oh my god this is awesome! however i don’t think it caches any requests like ExVCR so it’s not quite on topic. But I may use this in the future anyway, looks like a great idea.

this is a good idea in general, i think. but then it adds a lot of complexity that i don’t really want to spend my time on fixing later. also this is basically what ExVCR does (but they probably do it better) and yet i can’t really get that thing to work properly. and even when i do it’s kind of a black box and i’m not sure my own solution would be any better.

currently with my new clear way of separation of business and API concerns, i think i will need solution like this eventually. like if i want to run my tests more often.

edit: oh, i should add. i think i’m going add the flag (as a module property) to enable printing of responses from my central module, because i still need to write expectations to the tests.


thanks for chipping in everyone, you really sped up my thinking here. i will probably refer back here again and again in the coming years :smiley:

lud

lud

Probably but it relies on mocking where what I propose justs needs a if somewhere, which is much more simple and even if you are not confident in the fact that you can pull it out I’m sure you will very quickly see that you actually can.

Something like that could be a good start :

defmodule MyApi do
  case Application.compile_env(:my_app, :api_request_mode, :real) do
    :real ->
      def request(url, method, body, headers) do
        do_request(url, method, body, headers)
      end

    :record ->
      def request(url, method, body, headers) do
        file = hash_req(url, method, body, headers) <> ".json"
        resp = do_request(url, method, body, headers)
        record_response(resp, file)
      end

    :mock ->
      def request(url, method, body, headers) do
        file = hash_req(url, method, body, headers) <> ".json"
        mock_response(file)
      end
  end
end

No mocking library or macros to get in the way.

pdgonzalez872

pdgonzalez872

What about writing a test that hits the real thing/a sandbox? You’d load real keys, make it hit the real endpoints. These tests would only be run locally and would be excluded from a normal mix test run (what CI would run).

It’s basically capturing what you did in your IEx sessions/running the real code (however you were using the IO.inspect/1 calls). I’ve seen these types of tests save a lot of time for teams. Mostly because it’s documentation and something that anyone on the team can run. No passing scripts around, etc.

More info: Mocks and explicit contracts - Dashbit Blog, the

Along these lines (the article does a great job at showing these):

defmodule IntegrationTests do
  @moduletag :integration_tests

  ...
end

# test_helper.exs
ExUnit.start(exclude: [:integration_tests])
LostKobrakai

LostKobrakai

Very much that. Keep the code directly depending on the API shallow, e.g. just pulling out certain values out of individual requests and let the business logic/composition of those values work only with those known to exist values.

The more business logic you can decouple from the actual requests being made the simpler it will be to test.

Where Next?

Popular in Questions Top

sergio
In Ruby, I can go: User.find_by(email: "foobar@email.com").update(email: "hello@email.com") How can I do something similar in Elixir? ...
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
polypush135
As many of you may have realized by now (sorry for all the posts here) I’ve been working on a db problem where I’m trying to aggregate a ...
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
chensan
I have a User schema with a :from_id field set to type :string: defmodule TweetBot.Repo.Migrations.CreateUsers do use Ecto.Migration ...
New
mathew4509
I have a list say x = ["23gh", "56kh", "97mh"] I would like to pass each element to Val in each iteration. Say, in iteration 1 -------...
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 Postg...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New

Other popular topics Top

shahryarjb
Hello, I get Persian date from my client and convert it to normal calendar like this: def jalali_string_to_miladi_english_number(persi...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New
ycv005
I have followed this StackOverflow post to install the specific version of Erlang. And When I am running mix ecto.setup then getting fol...
New
New
quazar
How to set Jason to encode all fields in ecto schema, I don’t care about security and implementing only is taking long list of attributes...
New
qwerescape
Is there a way to get the call stack or stack trace at any point in the code? Not from exceptions, but an expression that returns how the...
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 Postg...
New
lucidguppy
I have a super simple question about elixir - how would I take a file like this foo bar baz and output a new file that enumerates th...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New

We're in Beta

About us Mission Statement