-
Notifications
You must be signed in to change notification settings - Fork 348
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Deprecate Tesla.Mock #241
Comments
Is there any value in it? One can simply call encode/* from json lib of choice. |
The value is json(%{"some" => "data"}) instead of %Tesla.Env{status: 200, headers: [{"content-type", "application/json"}], body: Jason.encode!(%{"some" => "data"})} |
Ah, ok. I have never used it before 😅 |
I'll test this approach thanks for posting it here. I have intermittent errors with
|
@victorolinasc Thanks! I can’t wait to hear some feedback. Please post your issues with current Tesla.Mock as a separate issue, together with code sample (let’s keep this issue focused on deprecation) |
I do find the As for So hopefully I can try it out tomorrow or on Friday and provide some feedback. |
I have tried out approach with Unless we will find some problem that I have not stumbled upon while playing around with it, we would probably prefer using @teamon thank you for this nice suggestion. :) UPD: almost forgot, I still find the |
@ivan-kolmychek Thank you for trying this out, your feedback is very valuable 👍 My biggest concern is the verbosity of Could you share some examples how do you use it? |
@teamon I have tried it pretty much how you described. Our use-case is relatively simple, so for us it's not that much more verbose than I can't post the actual code, so here is a short and "sanitized" version: In test helper:
Test:
I am pretty sure we can improve quite a few things, but it does the job for now. :) |
@victorolinasc did you ever fix or open a new thread in regards to this error? |
@polmiro Check #157 or use |
I found this issue when I had been looking for a way to test my app's behavior when |
Using Mox also proves to be quite good for us because it's so universal. For example, we use it now to test our emails that we send using Swoosh. |
Here's the full example: defmodule KiraTest.Usecases.QueueIssueFromNoteTest do
use Kira.DataCase
import Mox
import KiraTest.Factory
alias Kira.Projects.Services.Reactions.Providers.GitlabReaction
alias Kira.Usecases.QueueIssueFromNote
alias KiraTest.Projects.Services.Reactions.Providers.GitlabReaction.Mock
describe "create issue command" do
setup :verify_on_exit!
setup do
issue = insert(:issue)
note_iid = 123
Application.put_env(:tesla, :adapter, Mock)
{:ok, issue: issue, note_iid: note_iid}
end
test "valid queue command", %{issue: issue, note_iid: note_iid} do
issue_url = GitlabReaction.issue_reaction_url(issue)
issue_note_url = GitlabReaction.issue_note_reaction_url(issue, note_iid)
Mock
|> expect(:call, fn
%{method: :post, url: "https://gitlab.com/api/v4" <> ^issue_url},
_opts ->
{:ok, %Tesla.Env{status: 200}}
%{method: :post, url: "https://gitlab.com/api/v4" <> ^issue_note_url},
_opts ->
{:ok, %Tesla.Env{status: 200}}
end)
{:ok, context} =
QueueIssueFromNote.run(
project_uid: issue.project.uid,
issue_uid: issue.uid,
note_text: "@kira-bot queue",
note_iid: note_iid
)
assert context.entity.state == "queued"
end
end
end
ExUnit.start()
Ecto.Adapters.SQL.Sandbox.mode(Kira.Repo, :manual)
# Mocking different things:
# https://hexdocs.pm/mox/Mox.html
# https://github.com/teamon/tesla/issues/241
Mox.defmock(
KiraTest.Projects.Services.Reactions.Providers.GitlabReaction.Mock,
for: Tesla.Adapter
) Actual code: defmodule Kira.Projects.Services.Reactions.Providers.GitlabReaction do
@moduledoc """
HTTP client to award emojies to all possible kinds of Gitlab content.
Uses smart retry and timeout policy. Works syncronously.
For async version use:
Task.start(fn -> GitlabReaction.issue(some_issue) end)
This is something this module should not care about.
"""
use Tesla
@reaction "ok_hand"
@domain Application.get_env(:kira, :gitlab)[:domain]
@api_url "#{@domain}/api/v4"
@private_token Application.get_env(:kira, :gitlab)[:personal_token]
plug Tesla.Middleware.BaseUrl, @api_url
plug Tesla.Middleware.Headers, [{"Private-Token", @private_token}]
plug Tesla.Middleware.Query, name: @reaction
plug Tesla.Middleware.Timeout, timeout: 2000
plug Tesla.Middleware.Retry
# URL API:
def issue_reaction_url(issue) do
"#{issue_path(issue)}/award_emoji"
end
def issue_note_reaction_url(issue, note_iid) do
"#{issue_path(issue)}/notes/#{note_iid}/award_emoji"
end
# HTTP API:
def issue(issue) do
post!(issue_reaction_url(issue), [])
end
def issue_note(issue, note_iid) do
post!(issue_note_reaction_url(issue, note_iid), [])
end
# Private:
defp issue_path(issue) do
"/projects/#{issue.project.uid}/issues/#{issue.iid}"
end
end |
Hi @teamon sorry for the bother but I can't figure out the exact structure of you first example: # define as many mock adapters as you want
Mox.defmock(MyApp.NiceApi.Mock, for: Tesla.Adapter)
Mox.defmock(MyApp.EvilApi.Mock, for: Tesla.Adapter)
# config/test.exs
config :tesla, MyApp.NiceApi, adapter: MyApp.NiceApi.Mock
config :tesla, MyApp.EvilApi, adapter: MyApp.EvilApi.Mock
# test!
MyApp.NiceApi.Mock
|> expect(:call, fn
%{url: "http://github.com"}, _opts ->
{:ok, %Tesla.Env{status: 200, body: "ok"}}
%{url: "http://example.com"}, _opts ->
{:ok, %Tesla.Env{status: 500, body: "oups"}}
end)
MyApp.EvailApi.Mock
|> expect(:call, fn %{url: "http://noop"}, _opts ->
{:error, :econnrefused}
end) I mean, where does these 2 lines live? 🤔: # define as many mock adapters as you want
Mox.defmock(MyApp.NiceApi.Mock, for: Tesla.Adapter)
Mox.defmock(MyApp.EvilApi.Mock, for: Tesla.Adapter) I'm asking because from the all the other examples I clearly see that the most common approach is to modify the env inside the specs setup with # config/test.exs
config :tesla, MyApp.NiceApi, adapter: MyApp.NiceApi.Mock
config :tesla, MyApp.EvilApi, adapter: MyApp.EvilApi.Mock Just to give you a little bit of context I'm trying to test a module that relies on some other ones that wraps config :rest_clients, RestClients.ExternalService,
adapter: RestClients.ExternalService.Mock,
base_url: "http://localhost:8080/rest" All the modules, the wrapping ones, and the one under test are "separated" apps inside an umbrella. Sorry for if I've been vague but I'm a little bit confused about the whole env configs 😅 P.S: thanks a lot for you wonderful work and this incredible library 🙇 |
@fusillicode See mox docs for instruction on how to setup mocks |
@teamon thanks for your quick reply 🙇 I've already looked at it but unfortunately it didn't help much :( Anyway the specific thing that I can't understand is why everything seems to be mocked correctly (yes I know, I'm using a verb rather that a nown here) if I set the mock adapter for all the clients but not if I set it for my specific module (client), i.e: # config/test.exs
use Mix.Config
config :rest_clients, RestClients.ExternalService,
base_url: "http://localhost:8080/external-service"
config :tesla, RestClients.ExternalService, adapter: RestClients.ExternalService.Mock # <- this does not work
config :tesla, adapter: RestClients.ExternalService.Mock # <- this do work Btw this is the snippet of the test that is failing due to the "wrong" mock setup: ...
setup_all do
Mox.defmock(RestClients.ExternalService.Mock, for: Tesla.Adapter)
:ok
end
test "successful and correct API response with no process found" do
RestClients.ExternalService.Mock
|> expect(:call, fn %{url: "http://localhost:8080/external-service/process-definition/dummy-id/start", method: :post}, _opts -> {:ok, %Tesla.Env{status: 200}} end)
# just a bunch of assertions
...
P.S: sorry once again for the bother 🙏 |
This line config :tesla, RestClients.ExternalService, adapter: RestClients.ExternalService.Mock only works if you use module-based clients (with Please do NOT comment in this issue and open a new one if you need any more help. |
Would switching to Mox avoid the |
@garthk I'm not an expert, but I don't see how it would - by default Mox doesn't intercept/count calls from other processes, you need to explicitly allow it by passing the mock, pid of the current process and pid of the process that makes the call to To me it sounds like you may have to use Mox'es "global mode" unless you have a way to obtain the pid of process that's created by that I'm not sure how much in advance you have to do that, but my guess would be at least before the actual call to the mock happens. I hope I'm wrong and someone else knows an easier way to achieve what you're looking for without using global mode. |
You're right @ivan-kolmychek, if you have anything async you need to use global mode. |
Doc notes for people finding this through a search: In Elixir 1.8, Mox can peek at |
My 2 cents, don't remove the module. I just realized the module exists for mocking because I was scanning the issues but I use But I still use Here is a full example defmodule Okta.TestSupport.Helpers do
@moduledoc false
import ExUnit.Assertions
def mock_request(opts) do
mock_request(base_url(), opts)
end
def mock_request(base_url, opts, callback \\ nil) do
status = Keyword.get(opts, :status)
path = Keyword.get(opts, :path)
method = Keyword.get(opts, :method)
query = Keyword.get(opts, :query, [])
response = Keyword.get(opts, :response, %{})
body = Keyword.get(opts, :body)
Mox.expect(Okta.Tesla.Mock, :call, fn
request, _opts ->
assert(request.body == with_default_body(request, body))
assert(request.method == method)
assert(request.query == query)
assert(request.url == base_url <> path)
if callback do
callback.()
end
{:ok, Tesla.Mock.json(response, status: status)}
end)
end
def base_url do
"https://dev-000000.okta.com"
end
def with_default_body(%{method: method}, nil) when method in [:post, :put, :patch] do
""
end
def with_default_body(_, body) do
body
end
end in the test files alias Okta.TestSupport.Helpers
# ..........
Helpers.mock_request(
path: "/api/v1/users",
method: :post,
query: [activate: true],
body: Jason.encode!(%{profile: profile}),
status: 200
) |
👋 My couple of cents - I like Tesla.Mock and I also use it alongside Mox. Basically I have some business logic that is supposed to send API notifications. I use I then have separate tests for the Notification module where I use What I could see is deprecating this and moving it to a separate package that depends on mox to remove the duplication in tricks pulled, but to me it actually really does provide value 🤞 |
I think this part should be done with actual HTTP request/response processing - including hitting tesla adopters. Doing with only Tesla.Mock is not testing the whole thing. For example, we've seen strange behavior or bug from some library or adapters - such as httpc... For that reason, I think it's better to use bypass or similar tools, which spins up real HTTP server during tests, not Tesla.Mock, to test the actual HTTP interaction. See [ex_force test file] as an example. |
@chulkilee thanks for the input! I had actually assumed that I usually use bypass for this kind of thing but was happy that Tesla provided a mock interface which meant one less dependency for me (Tesla.Mock just seemed like a slightly more convenient bypass to me as I didn't have to make the URL injectable) but I might have to revisit that decision then. Thanks! 💚 |
I like the idea of Tesla using Mox. I know Mox and its quirks. E.g. allowances system for using it in spawned processes. I know its limitations, e.g. I can't mock stuff for global processes that are part of my supervision tree. Mox is a great, well-known abstraction. When I saw Functions like |
If Tesla.Mock isn't going away it seems like it's worth making it fail if a request is mocked and not called. Would a PR to do so be welcome? Is there some technical reason why this would be hard/impossible? |
Hello everyone I would like to reprecate Please share some feedback at #718 |
Please use |
Currently
Tesla.Mock
is implemented as adapter with special interface to intercept requests and provide mocked responses. It has a few drawbacks, like for example being a single instance - if you want to mock multiple clients you still have to use singleTesla.Mock.mock fn -> ...
block (#238).It does not verify if the requests were actually called or not. Under the hood it uses very similar tricks as mox. Mox requires a behaviour to be able to provide expectations. And actually I can be used instead of Tesla.Mock right now.
I am currently using this method in one project, so far with success.
We could keep helper functions like
Tesla.Mock.json/*
but we could probably remove the underlying mock machinery and promote using something like mox.The text was updated successfully, but these errors were encountered: