neuone
Overview
How to test my JSON API controller when a module inside of it relies on making a 3rd party api call.
Use the example from Bypass
I found an example of using Bypass. From the github repo they show an example of unit testing a Twitter client.
Controller Test
Take that example from the github repo of a TwitterClient and let’s create a controller and nest the TwitterClient inside of it. So that every time you visit the endpoint it sends out a tweet. Anytime I visit /api/tweet my controller will TwitterClient.post_tweet/1 and return the tweet we get back from twitter and render some json.
Controller
defmodule MyAppWeb.Api.TweetController do
use MyAppWeb, :controller
action_fallback MyAppWeb.Api.FallbackController
def index(conn, params) do
with {:ok, result} <- TwitterClient.post_tweet("Elixir is awesome!") do
render(conn, "show.json", tweet: result)
end
end
end
TestController
defmodule MyAppWeb.Api.TweetControllerTest do
use MyAppWeb.ConnCase
describe "GET /tweet" do
test "success, it sends a tweet", %{conn: conn} do
conn = get(conn, "/tweet")
assert json_response(conn, 200) == %{tweet: "Elixir is awesome"}
end
end
end
If I run this test "GET /tweet" the module TwitterClient is going to hit the live twitter website.
Question
How do I test this controller, but prevent the TwitterClient which I don’t own from actually making the live call to Twitter?
Within my test controller, am I supposed to intercept external HTTP request?
Do I need to read up on Mox?
Any direction, material articles would be appreciated.
Trending in Questions
Other Trending Topics
Latest Phoenix Threads
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #blog-post
- #ai
- #phoenix_html
- #iex
- #elixirconf-us
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #hex
- #security
- #metaprogramming











Showing Posts 1 to 10- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
trisolaran
I think you have essentially 2 options, and it all boils down to configuration:
Make your
TwitterClientconfigurable, so that in the test environment it will be started/configured with an endpoint URL pointing to the localBypassendpoint.Use
Moxand make sure, again via configuration, that yourTweetControlleruses the mocked version ofTwitterClientin testing.If you choose option 1, you will be testing both the controller and the client when testing the controller, and your test will be more “integration-like”. If you choose option 2, your test will be more isolated (you’ll be testing only the controller).
crispinb
Bear in mind you can only use Mox if your 3rd party TwitterClient defines a
@behaviour. Otherwise you’ll need to investigate other mocking libraries, or mock ad hoc in your tests. Here’s a useful article on the Mox approach: https://medium.com/flatiron-labs/elixir-test-mocking-with-mox-b825a955143fneuone
crispinb. Great article. This is helpful. Although I am overwhelmed reading docs on bypass and mox.
neuone
trisolaran
With option 1 I can’t figure from my controller test, how I would pass down the Bypass endpoint when my controller test is running.
The configuration is what I’m struggling with. With unit tests it’s easy because you can pass it right in. On a controller test I don’t see how I can pass in the Bypass endpoint from set up.
I think I’m going to go with option 2 with
Moxand see how far it takes me.trisolaran
I think that using Mox (or some other mocking library) makes sense in this case because, as you said, you don’t own the
TwitterClient, and so it’s not your job to test its implementation.But to answer your question about the configuration: looking at the example on GitHub that you shared, I see that the
TwitterClienthas to be started with a url option. Assuming that you’re starting it in your application’s supervision tree, you can do something like this:in your
lib/myapp/application.ex:in your
config/config.exs:in your
config/test.exs:Where
BYPASS_ENDPOINTis the local endpoint your bypass instance will be listening to. Then you can startBypassand set up the required expectations before testing the controller.benwilson512
You can simply wrap the TwitterClient module in a module of your own that defines a behavior, you don’t need the library to do so.
crispinb
True. Tastes vary on this, but while I find it reasonable (advisable!) to refactor towards testability, I wouldn’t myself choose to refactor just to use a specific mocking library. Of course there might be good reasons to do so in particular cases.
neuone
I like this idea of configuration through
config.exsandtest.exs.Revising the example
Based on your feedback, the new set up for the controller would use the
@endpointattribute and it would return a url based on the environment that it’s in.controller(revised)
How do I test this controller in my test controller?
test controller
dimitarvp
To alleviate the risk of falling into the trap of wishful thinking that mocking incurs, just use
curl(or Postman, Insomnia etc.) to download responses from the actual live API, save them on your machine and then make text fixtures out of them. Something like the following (or you can save them in CSV / XML / JSON andFile.read!them inside the mocking functions):Using
Moxis an advantage because it forces you to think of the contract of whatever you want to mock. @benwilson512 alludes to this by advising you to still contract your 3rd-party-consuming modules through using your own behaviour even if the original 3rd party library doesn’t offer one.You’ll find that as you are
Mox-enabling your tests (so to speak) you’ll dig up additional insights along the way. That’s valuable.crispinb
Excellent advice. This also encourages you to keep your mocks thin, and makes use of the API data easy to expand as needs dictate. Maybe this is just me, but I have a tendency to pluck the few values from the API return I think I need for my model, only to find later there’s gold there that I missed. With raw api returns available locally, everything’s always there to be mined later.