Packages

Dependency graphs for optimal function call ordering

Current section

Files

Jump to
pacer lib workflow testing.ex
Raw

lib/workflow/testing.ex

defmodule Pacer.Workflow.Testing do
@moduledoc """
This module provides testing helpers for dependencies and fields for Pacer.Workflow.
## How to use in tests
`use` this module in tests:
use Pacer.Workflow.Testing
This will allow you to use the helper assertions in your test module.
Examples:
```elixir
defmodule SomeWorkflowTest do
use Pacer.Workflow.Testing
defmodule TestGraph do
use Pacer.Workflow
graph do
field(:field_a)
field(:field_with_default,
virtual?: true,
default: "this is a default value"
)
batch :http_requests do
field(:request_1,
resolver: &__MODULE__.do_work/1,
dependencies: [:field_a],
default: 2
)
field(:request_2,
resolver: &__MODULE__.do_work/1,
dependencies: [:field_a],
default: "this is a default value for request2"
)
end
end
def do_work(_), do: :ok
end
describe "testing graph dependencies and fields" do
test "assert dependency of :field_a has no dependencies" do
assert_dependencies(TestGraph, :field_a, [])
end
test "assert batch dependency of :request_2" do
assert_batch_dependencies(TestGraph, :request_2, [:field_a])
end
test "provided fields match fields from workflow" do
assert_fields(TestGraph, [
:field_a,
:field_with_default,
:request_1,
:request_2
])
end
end
end
```
"""
import ExUnit.Assertions, only: [assert: 2]
defmacro __using__(_) do
quote do
alias Pacer.Workflow.Testing
def assert_dependencies(workflow, field, dependencies) do
Testing.assert_dependencies(workflow, field, dependencies)
end
def assert_batch_dependencies(workflow, batch_field, dependencies) do
Testing.assert_batch_dependencies(workflow, batch_field, dependencies)
end
def assert_fields(workflow, fields) do
Testing.assert_fields(workflow, fields)
end
end
end
@doc """
Test helper to assert on non-batch field dependencies.
First argument is your workflow, second is the field as an atom that you want to test,
third are the dependencies you are passing in to be dependencies for the field passed in as the second argument.
Note, the third argument should be a list of atoms or an empty list in the case of no dependencies.
## Example
If we have a workflow defined as:
```elixir
defmodule TestGraph do
use Pacer.Workflow
graph do
field(:field_a)
field(:field_b,
resolver: &__MODULE__.do_work/1,
dependencies: [:field_a]
)
end
end
```
We can test the dependencies of both fields:
```elixir
test "assert :field_a has no dependencies" do
assert_dependencies(TestGraph, :field_a, [])
end
test "assert dependencies of :field_b" do
assert_dependencies(TestGraph, :field_b, [:field_a])
end
```
"""
@spec assert_dependencies(module(), atom(), list(atom())) :: true | no_return()
def assert_dependencies(workflow, field, dependencies) do
found_dependencies = find_dependencies(workflow, field)
error_message = """
Expected #{inspect(field)} to have dependencies: #{inspect(dependencies)}.
Instead found: #{inspect(found_dependencies)}.
"""
assert dependencies_for_field(dependencies, found_dependencies),
error_message
end
@doc """
Test helper to assert on batch field dependencies.
First argument is your workflow, second is the batch field as an atom that you want to test,
third are the dependencies you are passing in to be dependencies for the field passed in as the second argument.
Note, the third argument should be a list of atoms or an empty list in the case of no dependencies.
## Example
If we have a workflow defined as:
```elixir
defmodule TestGraph do
use Pacer.Workflow
graph do
field(:field_a)
batch :http_requests do
field(:request_1,
resolver: &__MODULE__.do_work/1,
dependencies: [:field_a],
default: 2
)
field(:request_2,
resolver: &__MODULE__.do_work/1,
dependencies: [:field_a],
default: "this is a default value for request2"
)
end
end
def do_work(_), do: :ok
end
```
We can test the dependencies of both fields:
```elixir
test "assert batch fields have dependencies on :field_a" do
assert_batch_dependencies(TestGraph, :request_1, [:field_a])
assert_batch_dependencies(TestGraph, :request_2, [:field_a])
end
```
"""
@spec assert_batch_dependencies(module(), atom(), list(atom())) :: true | no_return()
def assert_batch_dependencies(workflow, batch_field, dependencies) do
found_dependencies = find_batch_dependencies(workflow, batch_field)
error_message = """
Expected batched field: #{inspect(batch_field)} to have dependencies: #{inspect(dependencies)}.
Instead found: #{inspect(found_dependencies)}.
"""
assert dependencies_for_field(dependencies, found_dependencies),
error_message
end
@doc """
Test helper to assert on the field defined in the workflow.
First argument is your workflow, second is a list of atoms that are fields in the workflow.
Ordering does not matter in the list of atoms since we use MapSets to do the comparison.
## Example
If we have a workflow defined as:
```elixir
defmodule TestGraph do
use Pacer.Workflow
graph do
field(:field_a)
field(:field_b,
resolver: &__MODULE__.do_work/1,
dependencies: [:field_a]
)
field(:field_with_default,
virtual?: true,
default: "this is a default value"
)
batch :http_requests do
field(:request_1,
resolver: &__MODULE__.do_work/1,
dependencies: [:field_a],
default: 2
)
field(:request_2,
resolver: &__MODULE__.do_work/1,
dependencies: [:field_a, :field_b],
default: "this is a default value for request2"
)
end
end
def do_work(_), do: :ok
end
```
We can test the fields:
```elixir
test "fields in the workflow" do
assert_fields(TestGraph, [:field_a, :field_b, :request_2, :request_1])
end
```
"""
@spec assert_fields(module(), list(atom())) :: true | no_return()
def assert_fields(workflow, fields) do
workflow_fields = workflow.__graph__(:fields)
error_message = """
Expected #{inspect(workflow)} to have fields: #{inspect(fields)}.
Instead found: #{inspect(workflow_fields)}.
"""
assert MapSet.equal?(MapSet.new(fields), MapSet.new(workflow_fields)), error_message
end
@spec find_dependencies(module(), atom()) :: list(atom()) | nil
defp find_dependencies(workflow, field) do
dependencies = workflow.__graph__(:dependencies)
dependencies
|> Enum.find(fn {found_field, _dependencies} ->
found_field == field
end)
|> case do
{_field, found_dependencies} -> found_dependencies
nil -> nil
end
end
@spec find_batch_dependencies(module(), atom()) :: list(atom()) | nil
defp find_batch_dependencies(workflow, field) do
dependencies = workflow.__graph__(:batch_dependencies)
dependencies
|> Enum.find(fn {_batch_name, found_field, _dependencies} ->
found_field == field
end)
|> case do
{_batch_name, _field, found_dependencies} -> found_dependencies
nil -> nil
end
end
@spec dependencies_for_field(list(atom()), list(atom()) | nil) :: boolean()
defp dependencies_for_field(_dependencies, nil), do: false
defp dependencies_for_field(dependencies, found_dependencies) do
MapSet.equal?(MapSet.new(dependencies), MapSet.new(found_dependencies))
end
end