Trouble scheduling Oban Jobs properly in tests

Hi,

I have the following worker:

defmodule MyApp.Workers.ProductWelcomeEmail do
  use Oban.Worker,
    max_attempts: 3,
    queue: :email,
    tags: ["product_welcome_email"]

  @impl Oban.Worker
  def perform(%Oban.Job{}) do
    :ok
  end
end

This executes fine in production, and schedules as expected. When calling it in a test:

IO.inspect(
  Workers.ProductWelcomeEmail.new(
     %{},
      schedule_in: 3600
    )
    |> Oban.insert()
)

I receive the following output:

{:ok,
 %Oban.Job{
   __meta__: #Ecto.Schema.Metadata<:built, "oban_jobs">,
   id: nil,
   state: "completed",
   queue: "email",
   worker: "MyApp.Workers.ProductWelcomeEmail",
   args: %{},
   meta: %{},
   tags: ["product_welcome_email"],
   errors: [],
   attempt: 1,
   attempted_by: ["Marks-MBP"],
   max_attempts: 3,
   priority: 0,
   attempted_at: ~U[2023-05-25 09:27:36.593744Z],
   cancelled_at: nil,
   completed_at: ~U[2023-05-25 09:27:36.642205Z],
   discarded_at: nil,
   inserted_at: nil,
   scheduled_at: ~U[2023-05-25 09:27:36.593745Z],
   conf: %Oban.Config{
     dispatch_cooldown: 5,
     engine: Oban.Engines.Basic,
     get_dynamic_repo: nil,
     log: false,
     name: Oban,
     node: "Marks-MBP",
     notifier: Oban.Notifiers.Postgres,
     peer: false,
     plugins: [],
     prefix: "public",
     queues: [],
     repo: MyApp.Repo,
     shutdown_grace_period: 15000,
     testing: :inline
   },
   conflict?: false,
   replace: nil,
   unique: nil,
   unsaved_error: nil
 }}

The job is executing immediately, meaning I can’t make any assertions on a worker being enqueued.
Putting an IO.inspect inside the worker’s logic confirms it’s being executed immediately upon being scheduled.

I’ve tried using both scheduled_at and schedule_in without any joy - and I’m triggering the code inside the test with:

    Oban.Testing.with_testing_mode(:manual, fn ->
        conn
        |> as_user(user)
        |> post(Routes.book_and_pay_path(conn, :complete_free_booking), %{
          "payment_id" => payment.id
        })
        |> json_response(200)
      end)

I believe using with_testing_mode(:manual) is the way to go but if that’s wrong please let me know, any advice on how to get the jobs scheduling properly in test mode is greatly appreciated.

1 Like

Just speculating here, but when you take a look at the implementation of the with_testing_mode function, it is storing some “flags” for the process it is called from.

I assume that your request runs (and calls Oban.insert) in a different process, so that the with_testing_mode has no effect on it.

I would try setting the testing mode to :manual globally in the config file and to run that particular test to make sure.

This is what I usually do in config/test.exs:

config :platform, Oban, plugins: false, queues: false, testing: :manual

And then just do Oban.insert like you do, and then in tests:

      assert_enqueued(
        worker: Your.Worker.Module,
        args: your_worker_args,
        queue: :your_job_queue
      )

And yeah, as @stefanluptak says it’s very likely that Oban.with_testing_mode is process-bound (though I haven’t changed).

Not super helpful advice I know, but globally using Oban’s :manual testing mode never gave me trouble so far.

Thanks for the above advice @stefanluptak and @dimitarvp

I can confirm changing the config to config :platform, Oban, plugins: false, queues: false, testing: :manual cleared my issue.

Unfortunately I have various other tests that seemingly relying on the testing: :inline setup, these ended up hanging and timing out.

I’ve tried to set the ‘global’ config within the setup block of the test itself with the pretty hacky

setup do
  base_config = Application.get_env(:my_app, Oban)
  custom_config = base_config ++ [plugins: false, queues: false, testing: :manual]
  Application.put_env(:my_app, Oban, custom_config)

  on_exit(fn ->
    Application.put_env(:my_app, Oban, base_config)
  end)
end

But these config tweaks don’t seem to be picked up mid test. As silly as this question sounds… is there any way I can set the global testing method to manual for just this one test file? Do I need to reload the config or should I just leave it globally at manual and fix the other tests that now timeout?

Thanks again,
Mark

It was probably obvious but wrapping the tests that timeout with

Oban.Testing.with_testing_mode(:inline, fn ->
end)

and leaving the global config at manual fixed the issue for me, thanks again for the help

2 Likes

btw, I know it’s obvious but just posting for posterity: :platform was a placeholder name (like :your_app would be). You have changed it to your app’s name, I know.

And yeah, your comment exactly before mine is how you do it. We too had a few inline tests in a previous contract and that’s exactly how we went about it.

1 Like

@MarkMekhaiel Great to see that you sorted it out!

One note about :testing modes—you don’t need to set plugins: false or queues: false because testing modes take care of that for you. Thus, you can leave your plugins and queues intact to get full config validation in test mode by simply changing one attribute in the test environment:

# test.exs
config :my_app, Oban, testing: :manual
2 Likes