andreyuhai
Oban how to properly assert on enqueued jobs?
I am trying to insert another Oban job after one finishes by listening to [:oban, :job, :stop].
I’d like to assert that the next job was queued after one succeeded, so in my test I do something like this ![]()
test "schedules job after another" do
# ...
{:ok, result} =
perform_job(Workers.MyWorker, %{
record_id: some_record_id
})
assert_enqueued(
[worker: Workers.SecondWorker, args: %{result_id: result.id}],
1000
)
end
And my telemetry event handler looks like this
def handle_event(
[:oban, :job, :stop],
_measure,
meta = %{worker: worker, result: {:ok, result}},
_
) do
Workers.SecondWorker.new(%{result_id: result.id}) |> Oban.insert()
end
However even though I can see the job inserted after inspecting the result of Oban.insert (though it doesn’t have an ID) my test still fails and I couldn’t really figure out why.
Most Liked
LostKobrakai
The point here is much more about this:
The code emitting telemetry events can’t handle problems of listeners – those can be libraries, which have no relationship to your project or metric setup. So it’s the job of listeners to have their shit together and deal with any issues they encounter.
al2o3cr
Since a crashing handler crashes the calling process, I assume the design principle is “prefer uptime over metrics” which isn’t unreasonable.
It can be kinda painful if you mess up, for instance, a listener that tracks Oban.Job crashes and sends them to Sentry so that you don’t get any reports from production after the very first one. Ask me how I learned THAT one ![]()
trisolaran
That’s expected, perform_job/3 doesn’t insert the job in the DB (it’s used to unit test a worker) so the id of the job will be nil
Yeah I verified this error with my own code and I was able to reproduce it.
Here’s what I strongly believe it’s going on:
:inlinetesting mode uses theOban.Queue.InlineEngine, which doesn’t touch the DB, while:manualtesting mode uses theOban.Queue.BasicEngine, which inserts jobs in the DBperform_job/3is always supposed to run in:inlinemode using theInlineEngine, if it accidentally uses theBasicEngine, the error you encountered will be triggered, namely it will try to update the state of a job that was never inserted in the DB- Therefore, when you call
perform_job/3, it setstesting: :inlinein the config here: oban/lib/oban/testing.ex at fdeb0001bbacb60c4686b86ca0610c7f7508d357 · sorentwo/oban · GitHub which is then used here: oban/lib/oban/config.ex at fdeb0001bbacb60c4686b86ca0610c7f7508d357 · sorentwo/oban · GitHub to enforce the usage of theInlineEngine(the correct one), regardless of thetestingmode set in your main config. So you can havetesting: :manualin your config and still haveperform_job/3run correctly using theInlineEngine. - This works well as long as you don’t use
with_testing_mode(:manual, ...). Reason is thatwith_testing_mode/2sets the engine to use in the process dictionary here: oban/lib/oban/testing.ex at 9b4861354f0189d548f4d5cd89273bc98f8eaede · sorentwo/oban · GitHub and this takes precedence over the engine set byperform_job/3. The overwrite happens here: oban/lib/oban/queue/engine.ex at 1e0f61a913a2ba52c985675383e2f7a180119ac5 · sorentwo/oban · GitHub
Long story short: perform_job/3 always tries to use the InlineEngine, cause it fails otherwise, but with_testing_mode/2 gets in the way end enforces the usage of the BasicEngine, which is the wrong one and causes the issue.
This seems to be a bug, perform_job/3 should ignore the engine set by with_testing_mode/2. Although, to be honest, I see little use for explicitly setting the test mode to :manual and then calling perform_job/3. If you’re using manual mode you’re expected to insert jobs in the DB and execute them with drain_queue/2.







