andreyuhai

andreyuhai

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 :point_down:

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.

Showing Posts 1 to 10

andreyuhai

andreyuhai OP

If I change the Oban config to be

config :lead_engine, Oban, testing: :manual

then it works but I don’t know whether there’s any other way of making this work because now some other tests fail. :sweat_smile:

If I wrap only my test case with with_testing_mode(:manual... then I get

     ** (ArgumentError) nil given for `id`. comparison with nil is forbidden as it is unsafe. Instead write a query with is_nil/1, for example: is_nil(s.id)
al2o3cr

al2o3cr

IIRC :telemetry handlers run in a separate process, you may be encountering DB sandbox issues. I did NOT recall correctly, see @LostKobrakai’s note below.

Also beware: telemetry doesn’t provide any delivery guarantees and aggressively (silently + permanently for the VM’s lifetime) removes listeners that crash even once. If SecondWorker is doing anything important, you may want to consider an alternative approach like Oban Pro’s workflows.

LostKobrakai

LostKobrakai

They do not, as they don’t want to introduce message passing.

andreyuhai

andreyuhai OP

Also beware: telemetry doesn’t provide any delivery guarantees and aggressively (silently + permanently for the VM’s lifetime) removes listeners that crash even once. If SecondWorker is doing anything important, you may want to consider an alternative approach like Oban Pro’s workflows.

I was guessing that those are GenServers and somehow they come back up, but yes, I’ve already encountered that during my tests, so we’ll probably have to switch to Pro to be able to use Workflows. Thank you!

I wonder how other people use it though, I mean if there’s no guarantee and the listeners might get removed. Is it because it doesn’t hurt much (well maybe it does, I have no idea :stuck_out_tongue:) losing some metrics?

al2o3cr

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 :stuck_out_tongue:

LostKobrakai

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 **** together and deal with any issues they encounter.

trisolaran

trisolaran

Correct, because in :inline mode, which is what I assume your original config was using, no jobs are ever enqueued in the DB

andreyuhai

andreyuhai OP

But then as I mentioned, even if I wrap it with with_testing_mode(:manual... I get that argument error I shared above.

trisolaran

trisolaran

Most likely because, as the error message suggests, perform_job is returning a result struct with id = nil.

Take a look at what happens in your worker or share the code so someone can help you.

andreyuhai

andreyuhai OP

It wasn’t actually about the result perform_job returns but that happens when Oban tries to complete the job, at least that’s what I understood from stacktrace.

  1) test handle_event/4 schedules the next job in the pipeline after one finishes (MyApp.ObanTelemetryHandlerTest)
     apps/my_app/test/my_app/oban_telemetry_handler_test.exs:16
     ** (ArgumentError) nil given for `id`. comparison with nil is forbidden as it is unsafe. Instead write a query with is_nil/1, for example: is_nil(s.id)
     code: Oban.Testing.with_testing_mode(:manual, fn ->
     stacktrace:
       (ecto 3.8.3) lib/ecto/query/builder/filter.ex:191: Ecto.Query.Builder.Filter.not_nil!/2
       (oban 2.12.1) lib/oban/queue/basic_engine.ex:137: Oban.Queue.BasicEngine.complete_job/2
       (oban 2.12.1) lib/oban/queue/engine.ex:234: anonymous fn/3 in Oban.Queue.Engine.with_span/4
       (telemetry 0.4.3) /Users/system/apps/my-app/deps/telemetry/src/telemetry.erl:272: :telemetry.span/3
       (oban 2.12.1) lib/oban/queue/executor.ex:203: Oban.Queue.Executor.ack_event/1
       (oban 2.12.1) lib/oban/queue/executor.ex:197: Oban.Queue.Executor.report_finished/1
       (oban 2.12.1) lib/oban/queue/executor.ex:80: anonymous fn/1 in Oban.Queue.Executor.call/1
       (oban 2.12.1) lib/oban/testing.ex:236: Oban.Testing.perform_job/3
       test/my_app/oban_telemetry_handler_test.exs:26: anonymous fn/1 in MyApp.ObanTelemetryHandlerTest."test handle_event/4 schedules the next job in the pipeline after one finishes"/1
       (oban 2.12.1) lib/oban/testing.ex:398: Oban.Testing.with_testing_mode/2
       test/my_app/oban_telemetry_handler_test.exs:24: (test)

Also when I’ve inspected the actual Oban.Job that was passed to perform/1 from perform_job, I see that the id is nil.

Not sure that’s because of perform_job but I do not think so (or maybe :thinking:). I mean perform_job doesn’t actually insert anything as far as I understood by looking at the module.

I’m also using perform_job in other tests but I’ve never encountered the same error.


Edit

Somehow using perform_job within with_testing_mode(:manual... causes some problems

      # ...
     with_testing_mode(:manual, fn ->
      {:ok, result} =
        perform_job(Workers.MyWorker, %{
          record_id: some_record_id
        })

      assert_enqueued(
        [worker: Workers.SecondWorker, args: %{result_id: result.id}],
        1000
      ) 
     end)

The above snippet somehow throws the ArgumentError I shared above.

I’ve also tried to use Oban.insert/1 instead of using perform_job/1 hoping that it would insert the job and once my job is handled it would enqueue another one :point_down:

      # ...
     with_testing_mode(:manual, fn ->
      %{record_id: some_record_id}
      |> Workers.MyWorker.new()
      |> Oban.insert()

      assert_enqueued(
        [worker: Workers.SecondWorker, args: %{result_id: result.id}],
        1000
      ) 
     end)

but somehow this way my first job didn’t even get processed. At least I didn’t see the IO.inspects that I did to ensure it was working the way I assumed it would.

how I could finally make it work was by directly calling perform/1 on my worker :point_down:

      # ...
     with_testing_mode(:manual, fn ->
      Workers.MyWorker.perform(%Oban.Job{args: %{record_id: some_record_id}})

      assert_enqueued(
        [worker: Workers.SecondWorker, args: %{result_id: result.id}],
        1000
      ) 
     end)

But at that point, is this test useful or necessary? Because I am calling perform/1 directly and I already know that if the job is succesfull the next thing it will do is to schedule another one. Maybe that’s the topic of another question about testing in general.

Where Next? Top

Trending in Questions Top

stjefim
Hello! Suppose you are building workflow (order / task / payment) processing system with the following requirements: Each workflow con...
New
jonnycharles
I’m in search of an Elixir library that offers PDF generation capabilities similar to Ruby’s Prawn. While there have been discussions abo...
New
spammy
I’m looking to build a personal workflow to quickly deploy web applications written in elixir/phoenix, for local consumption (ie not on t...
New
dli
Before I dive in myself, did anyone successfully sprinkle Hologram into their existing LiveView app? Looking for hints regarding: Addi...
New
roeland
Kia ora, We have been using elixir-google-api to connect to Google Drive. However, with the updates to Tesla due to CVEs this is now bro...
New
bottlenecked
Hi all, I wanted to ask how the community is dealing with post-release steps. Today we have Ecto migrations, which make sure that the db...
New
rahultumpala
Hello, I have an Elixir backend that implements a custom protocol over TCP. I want to load test the backend and assess the performance o...
New

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
ausimian
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews