Skip to content

Commit

Permalink
Add build_job/3 helper for easier testing
Browse files Browse the repository at this point in the history
Extract the mechanism for verifying and building jobs out of
`perform_job/3` so that it's usable in isolation. This also introduces
`perform_job/2` for executing built jobs.

Closes #1147
  • Loading branch information
sorentwo committed Aug 31, 2024
1 parent 4ea075f commit fae3762
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 33 deletions.
109 changes: 79 additions & 30 deletions lib/oban/testing.ex
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ defmodule Oban.Testing do
quote do
alias Oban.Testing

defdelegate build_job(worker, args, opts \\ []), to: Testing

def perform_job(worker, args, opts \\ []) do
opts = Keyword.merge(unquote(repo_opts), opts)

Expand Down Expand Up @@ -159,6 +161,79 @@ defmodule Oban.Testing do
end
end

@doc """
Construct a job from a worker, args, and options.
The helper makes the following assertions:
* That the worker implements the `Oban.Worker` behaviour
* That the options provided build a valid job
This helper is used to build jobs for execution by `perform_job/2`.
## Examples
Build a job without args:
job = build_job(MyWorker, %{})
Build a job with stringified args:
assert %{args: %{"id" => 1}} = build_job(MyWorker, %{id: 1})
Build a job with custom options:
assert %{attempt: 5, priority: 9} = build_job(MyWorker, %{}, attempt: 5, priority: 9)
"""
@doc since: "2.19.0"
@spec build_job(worker :: Worker.t(), args :: term(), [Job.option()]) :: Job.t()
def build_job(worker, args, opts) when is_atom(worker) do
assert_valid_worker(worker)

utc_now = DateTime.utc_now()

changeset =
args
|> worker.new(opts)
|> Changeset.update_change(:args, &json_recode/1)
|> Changeset.update_change(:meta, &json_recode/1)
|> put_new_change(:id, System.unique_integer([:positive]))
|> put_new_change(:attempt, 1)
|> put_new_change(:attempted_at, utc_now)
|> put_new_change(:scheduled_at, utc_now)
|> put_new_change(:inserted_at, utc_now)

assert_valid_changeset(changeset)

Changeset.apply_action!(changeset, :insert)
end

@doc """
Execute a job using the given config options.
See `perform_job/3` for more details and examples.
## Examples
Eexecute a job without any options:
assert :ok = perform_job(job, [])
Execute a job with a custom prefix and repo:
assert :ok = perform_job(job, prefix: "private", repo: MyApp.Repo)
"""
@doc since: "2.19.0"
@spec perform_job(Job.t(), [Oban.option()]) :: Worker.result()
def perform_job(%Job{} = job, conf_opts) when is_list(conf_opts) do
conf_opts
|> Config.new()
|> Executor.new(job, safe: false, ack: false)
|> Executor.call()
|> Map.fetch!(:result)
|> tap(&assert_valid_result/1)
end

@doc """
Construct a job and execute it with a worker module.
Expand All @@ -172,7 +247,7 @@ defmodule Oban.Testing do
* That the options provided build a valid job
* That the return is valid, e.g. `:ok`, `{:ok, value}`, `{:error, value}` etc.
If all of the assertions pass then the function returns the result of `perform/1` for you to
If all of the assertions pass, then the function returns the result of `perform/1` for you to
make additional assertions on.
## Examples
Expand Down Expand Up @@ -200,37 +275,11 @@ defmodule Oban.Testing do
@doc since: "2.0.0"
@spec perform_job(worker :: Worker.t(), args :: term(), [perform_opts()]) :: Worker.result()
def perform_job(worker, args, opts) when is_atom(worker) do
assert_valid_worker(worker)

{conf_opts, opts} = Keyword.split(opts, @conf_keys)

utc_now = DateTime.utc_now()

changeset =
args
|> worker.new(opts)
|> Changeset.put_change(:id, System.unique_integer([:positive]))
|> Changeset.update_change(:args, &json_recode/1)
|> Changeset.update_change(:meta, &json_recode/1)
|> put_new_change(:attempt, 1)
|> put_new_change(:attempted_at, utc_now)
|> put_new_change(:scheduled_at, utc_now)
|> put_new_change(:inserted_at, utc_now)

assert_valid_changeset(changeset)

job = Changeset.apply_action!(changeset, :insert)

result =
conf_opts
|> Config.new()
|> Executor.new(job, safe: false, ack: false)
|> Executor.call()
|> Map.fetch!(:result)

assert_valid_result(result)

result
worker
|> build_job(args, opts)
|> perform_job(conf_opts)
end

@doc """
Expand Down
16 changes: 13 additions & 3 deletions test/oban/testing_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,18 @@ defmodule Oban.TestingTest do
end
end

describe "build_job/3" do
test "creating a valid job changeset out of the args and options" do
job = build_job(Worker, %{id: 1}, meta: %{foo: "bar"})

assert %{args: args, id: id, meta: meta, worker: "Oban.Integration.Worker"} = job

assert is_integer(id)
assert %{"id" => 1} = args
assert %{"foo" => "bar"} = meta
end
end

describe "perform_job/3" do
test "verifying that the worker implements the Oban.Worker behaviour" do
message = "worker to be a module that implements"
Expand All @@ -100,9 +112,7 @@ defmodule Oban.TestingTest do
:ok = perform_job(DoubleBehaviourWorker, %{})
end

test "creating a valid job out of the args and options" do
assert_perform_error(Worker, %{}, [max_attempts: -1], "args and opts to build a valid job")

test "validating options used to construct a job changeset" do
assert_perform_error(
Worker,
%{},
Expand Down

0 comments on commit fae3762

Please sign in to comment.