-
Notifications
You must be signed in to change notification settings - Fork 353
Fix mocked calls failing when using Timeout middleware #574
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
Conversation
mock = Tesla.Mock.pdict_get() | ||
task = safe_async(task_module, fn -> | ||
if mock, do: Tesla.Mock.pdict_set(mock) | ||
Tesla.run(env, next) | ||
end) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey there, this seems a no-go from my perspective, Tesla.Mock
should not
be used in production code as such.
We need to find a better alternative. In this case, you can swap the Task
module implementation using task_module
configuration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can understand that, for sure. Unfortunately I'm using the OpenTelemetry task propagator too so this is the only thing that works for my use case; it's the only simple solution that works generically for all task modules. I know that it feels icky to use Tesla.Mock
in a production module, but it's simple, it solves the problem, and it doesn't have any nasty side effects.
Maybe we could find a generic way to transfer state into the task that the production code can implement and that Telsa.Mock
can use? Perhaps there's some way to build a feature into the adapter behavior, since maintaining state between processes is a requirement of Tesla.Mock
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We introduced a way to change the Task Module
here #568
Maybe swap the implementation of the task module as following (kind of I hope you get the idea):
# somewhere in the test/support dir
defmodule MyClient do
use Tesla
plug Tesla.Middleware.Timeout,
# notice here
task_module: TestSupport.MockTaskModule
end
Where the TestSupport.MockTaskModule
could be something around the lines of:
# inside test/support dir
defmodule TestSupport.MockTaskModule do
def async(fn) do
mock = Tesla.Mock.pdict_get()
Task.async(fn ->
# do it here
Tesla.Mock.pdict_set(mock)
{:ok, fn.()}
end)
end
defdelegate await(task, timeout), to: Task
defdelegate shutdown(task, timeout), to: Task
end
---
Also, what issue is this fixing per se? We dont have any failing tests, do we? Not sure what is broken, if you dont mind making it fail to see the issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a test in test/tesla/middleware/timeout_test.exs
that triggers this. The issue is that if you've set up a client with the Timeout
middleware, mocks made in tests do not work, since the Timeout middleware moves everything to another process, and the Tesla.Mock
adapter only checks the pdict for its current process.
I don't think I understand this solution; the Timeout middleware is in production code and I can't swap out the configured task_module
module during tests, can I?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't swap out the configured task_module module during tests, can I?
In the testing environment, yeah, you can. Notice here
tesla/test/tesla/middleware/timeout_test.exs
Lines 52 to 70 in 04a94e6
defmodule OtelTimeoutClient do | |
use Tesla | |
plug Tesla.Middleware.Timeout, | |
timeout: 100, | |
task_module: OpentelemetryProcessPropagator.Task | |
adapter fn env -> | |
case env.url do | |
"/sleep_50ms" -> | |
Process.sleep(50) | |
{:ok, %{env | status: 200}} | |
"/sleep_150ms" -> | |
Process.sleep(150) | |
{:ok, %{env | status: 200}} | |
end | |
end | |
end |
Add a new module called Tesla.Mock.Task
that is almost the exact same thing I shared with you before.
You can use that Tesla.Mock.Task
in your testing environment by swapping the task_module
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you suggesting that I use an entirely different client module during tests?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, one that does the mock thingy you need in order to make the test to pass
Since the Timeout middleware is creating a new process, the mock in the pdict is lost. Fortunately it's easy to copy the pdict over to the new process.