Skip to content

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

Closed
wants to merge 1 commit into from
Closed

Fix mocked calls failing when using Timeout middleware #574

wants to merge 1 commit into from

Conversation

Cantido
Copy link

@Cantido Cantido commented Apr 25, 2023

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.

Comment on lines +43 to +47
mock = Tesla.Mock.pdict_get()
task = safe_async(task_module, fn ->
if mock, do: Tesla.Mock.pdict_set(mock)
Tesla.run(env, next)
end)
Copy link
Member

@yordis yordis Apr 26, 2023

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.

Copy link
Author

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?

Copy link
Member

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.

Copy link
Author

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?

Copy link
Member

@yordis yordis Apr 27, 2023

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

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

Copy link
Author

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?

Copy link
Member

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants