Spark.Test (spark v2.7.0)

Copy Markdown View Source

ExUnit helpers for testing Spark DSL verifiers.

Verifier errors raised inside Spark's @after_verify hook are converted to stderr warnings by the framework rather than propagated as exceptions, so the usual assert_raise/2 testing pattern is unsuitable. This module exposes a structured alternative: register the test process as a collector, define DSL modules, and receive their Spark.Error.DslError values as data.

Helpers

  • dsl_errors/1 — runs a do-block and returns all collected verifier errors as [{module, [%Spark.Error.DslError{}]}, ...]. The error-side primitive.
  • dsl_warnings/1 — runs a do-block and returns all collected verifier warnings as [{module, [{message, location | nil}, ...]}, ...]. The warning-side primitive.
  • assert_dsl_error/2 — asserts that the do-block produces at least one error matching a given pattern and returns the matched error.
  • assert_dsl_error/1 — like /2 with a wildcard pattern: asserts at least one error of any kind.
  • assert_dsl_warning/2 — asserts that the do-block produces at least one warning matching a given pattern and returns the matched payload.
  • assert_dsl_warning/1 — like /2 with a wildcard pattern: asserts at least one warning of any kind.
  • refute_dsl_errors/1 — asserts that the do-block produces no errors at all. Use for happy-path tests.
  • refute_dsl_warnings/1 — asserts that the do-block produces no warnings at all.

Examples

iex> Spark.Test.dsl_errors(do: :ok)
[]

iex> Spark.Test.dsl_warnings(do: :ok)
[]

See dsl_errors/1, dsl_warnings/1, assert_dsl_error/2, and refute_dsl_errors/1 for full usage.

Async safety

All helpers in this module are safe to use from async: true ExUnit tests. The collection mechanism uses per-process state (Process.put/2 and point-to-point send/2) and does not cross process boundaries.

Summary

Functions

Asserts that the given do-block produces at least one verifier error and returns the first one.

Asserts that the given do-block produces at least one verifier error matching pattern and returns the matched error.

Asserts that the given do-block produces at least one verifier warning and returns the first one.

Asserts that the given do-block produces at least one verifier warning matching pattern and returns the matched payload.

Collects verifier errors from any Spark DSL modules defined inside the given do-block.

Collects verifier warnings from any Spark DSL modules defined inside the given do-block.

Asserts that the given do-block produces no verifier errors.

Asserts that the given do-block produces no verifier warnings.

Functions

assert_dsl_error(list)

(macro)

Asserts that the given do-block produces at least one verifier error and returns the first one.

Equivalent to assert_dsl_error/2 with a wildcard pattern: any Spark.Error.DslError matches. See assert_dsl_error/2 for details on failure semantics, refute_dsl_errors/1 for the inverse assertion, and assert_dsl_warning/1 for the warning-side counterpart.

assert_dsl_error(pattern, list)

(macro)

Asserts that the given do-block produces at least one verifier error matching pattern and returns the matched error.

pattern is matched against each Spark.Error.DslError in definition order via match?/2. The first match is returned. If no error matches, the test fails with a message that includes both the source form of the pattern and the inspected list of all collected errors.

Examples

err =
  assert_dsl_error %Spark.Error.DslError{path: [:fields, :email]} do
    defmodule Elixir.MyExample do
      use MyApp.Validator
      # ... configuration that triggers the verifier
    end
  end

assert err.message =~ "invalid email"

See dsl_errors/1 for the underlying collection mechanism, including the rules for module names defined inside the do-block.

assert_dsl_warning(list)

(macro)

Asserts that the given do-block produces at least one verifier warning and returns the first one.

Equivalent to assert_dsl_warning/2 with a wildcard pattern: any warning payload matches. See assert_dsl_warning/2 for details on failure semantics, and assert_dsl_error/1 for the error-side counterpart.

assert_dsl_warning(pattern, list)

(macro)

Asserts that the given do-block produces at least one verifier warning matching pattern and returns the matched payload.

Each warning payload is a {message, location | nil} tuple. pattern is matched against each payload in definition order via match?/2. The first match is returned. If no payload matches, the test fails with a message that includes both the source form of the pattern and the inspected list of all collected warnings.

Examples

{message, _location} =
  assert_dsl_warning {_, _} do
    defmodule Elixir.MyExample do
      use MyApp.Validator
      # ... configuration that triggers the warning
    end
  end

assert message =~ "deprecated"

See dsl_warnings/1 for the underlying collection mechanism, including the normalization of bare-string warnings to {message, nil} tuples.

dsl_errors(list)

(macro)

Collects verifier errors from any Spark DSL modules defined inside the given do-block.

Returns a list of {module, [%Spark.Error.DslError{}]} tuples in definition order. Modules that compile cleanly produce no entry. Stderr output emitted by verifiers (notably {:warn, _} returns) is suppressed during the call.

Examples

No DSL modules → empty list:

errors = dsl_errors do
  :ok
end
# => []

A failing DSL module → its errors:

errors = dsl_errors do
  defmodule Elixir.MyExample do
    use MyApp.Validator

    fields do
      # invalid configuration here
    end
  end
end

[{MyExample, [%Spark.Error.DslError{} | _]}] = errors

Module names defined inside the do-block

Modules whose errors you intend to assert on must be defined with the fully-qualified Elixir.SomeName form. A bare alias inside the macro's callback is resolved against the surrounding test module's scope, while the same alias outside the call is resolved at the top level — without the Elixir. prefix the two atoms would not match.

Cleanup

The collector flag is restored to its prior value (or unset if there was no prior value) when the call returns or when the block raises. Stale messages are drained from the test process's mailbox so subsequent calls are not contaminated.

See Spark.Dsl.Verifier for the verifier callback contract.

dsl_warnings(list)

(macro)

Collects verifier warnings from any Spark DSL modules defined inside the given do-block.

Returns a list of {module, [{message, location | nil}, ...]} tuples in definition order. Modules that compile cleanly produce no entry. The payloads are normalized: bare-string warnings become {message, nil}, tuple-form warnings preserve their :erl_anno location.

Stderr output emitted from the block is suppressed during the call.

Examples

No DSL modules → empty list:

warnings = dsl_warnings do
  :ok
end
# => []

A DSL module with a warning verifier → its payloads:

warnings = dsl_warnings do
  defmodule Elixir.MyExample do
    use MyApp.Validator
    # ... configuration that triggers the warning
  end
end

[{MyExample, [{message, _location} | _]}] = warnings

See dsl_errors/1 for the symmetric error-collection helper, and Spark.Dsl.Verifier for the verifier callback contract — including the shape of {:warn, _} returns.

refute_dsl_errors(list)

(macro)

Asserts that the given do-block produces no verifier errors.

Returns :ok on success. If any Spark.Error.DslError values are collected, the test fails with a message that includes the inspected [{module, [errors]}] list, so the offending modules and their errors are visible in the failure output.

Use this for happy-path tests where you want to assert that a DSL definition compiles cleanly through all verifiers.

Example

test "valid configuration is accepted" do
  refute_dsl_errors do
    defmodule Elixir.MyExample do
      use MyApp.Validator
      # ... valid configuration
    end
  end
end

See dsl_errors/1 if you need access to the full collected list (e.g. when asserting on multiple distinct errors at once), and refute_dsl_warnings/1 for the warning-side counterpart.

refute_dsl_warnings(list)

(macro)

Asserts that the given do-block produces no verifier warnings.

Returns :ok on success. If any warning payloads are collected, the test fails with a message that includes the inspected [{module, [{message, location | nil}, ...]}] list, so the offending modules and their warnings are visible in the failure output.

Use this for happy-path tests where you want to assert that a DSL definition compiles cleanly through all warning-emitting verifiers.

Example

test "no deprecated options used" do
  refute_dsl_warnings do
    defmodule Elixir.MyExample do
      use MyApp.Validator
      # ... configuration that uses no deprecated options
    end
  end
end

See dsl_warnings/1 if you need access to the full collected list, and refute_dsl_errors/1 for the error-side counterpart.