View Source Finitomata.ExUnit (Finitomata v0.20.1)

Helpers and assertions to make Finitomata implementation easily testable.

Summary

Functions

Convenience macro to assert a transition initiated by event_payload argument on the FSM defined by the test context previously setup with a call to setup_finitomata/1.

Convenience macro to assert a transition initiated by event_payload argument on the FSM defined by first three arguments.

This macro initiates the FSM implementation specified by arguments passed.

Setups Finitomata for testing in the case and/or in ExUnit.Case.describe/2 block.

Convenience macro to test the whole Finitomata path, from starting to ending state.

Functions

Link to this macro

assert_transition(ctx, event_payload, list)

View Source (macro)

Convenience macro to assert a transition initiated by event_payload argument on the FSM defined by the test context previously setup with a call to setup_finitomata/1.

Last regular argument in a call to assert_transition/3 would be an event_payload in a form of {event, payload}, or just event for no payload.

to_state argument would be matched to the resulting state of the transition, and block accepts validation of the payload after transition in a form of

test "some", ctx do
  assert_transition ctx, {:increase, 1} do
    :counted ->
      assert_payload do
        user_data.counter ~> 2
        internals.pid ~> ^parent
      end
      # or: assert_payload %{user_data: %{counter: 2}, internals: %{pid: ^parent}}

      assert_receive {:increased, 2}
  end
end
Link to this macro

assert_transition(id \\ nil, impl, name, event_payload, list)

View Source (macro)

Convenience macro to assert a transition initiated by event_payload argument on the FSM defined by first three arguments.

NB it’s not recommended to use low-level helpers, normally one should define an FSM in setup_finitomata/1 block and use assert_transition/3 or even better test_path/3.

Last regular argument in a call to assert_transition/3 would be an event_payload in a form of {event, payload}, or just event for no payload.

to_state argument would be matched to the resulting state of the transition, and block accepts validation of the payload after transition in a form of

parent = self()

assert_transition id, impl, name, {:increase, 1} do
  :counted ->
    assert_payload do
      user_data.counter ~> 2
      internals.pid ~> ^parent
    end
    # or: assert_payload %{user_data: %{counter: 2}, internals: %{pid: ^parent}}
    assert_receive {:increased, 2}
end
Link to this macro

init_finitomata(id \\ nil, impl, name, payload, options \\ [])

View Source (macro)

This macro initiates the FSM implementation specified by arguments passed.

NB it’s not recommended to use low-level helpers, normally one should define an FSM in setup_finitomata/1 block, which would initiate the FSM amongs other things.

Arguments:

  • id — a Finitomata instance, carrying multiple _FSM_s
  • impl — the module implementing FSM (having use Finitomata clause)
  • name — the name of the FSM
  • payload — the initial payload for this FSM
  • options — the options to control the test, such as
    • transition_count — the number of expectations to declare (defaults to number of states)

Once called, this macro will start Finitomata.Suprevisor with the id given, define a mox for impl unless already efined, Mox.allow/3 the FSM to call testing process, and expectations as a listener to after_transition/3 callback, sending a message of a shape {:on_transition, id, state, payload} to test process.

Then it’ll start FSM and ensure it has entered Finitomata.Transition.entry/2 state.

Link to this macro

setup_finitomata(list)

View Source (macro)

Setups Finitomata for testing in the case and/or in ExUnit.Case.describe/2 block.

It would effectively init the FSM with an underlying call to init_finitomata/5, and put finitomata key into context, assigning :test_pid subkey to the pid of the running test process, and mixing :context content into test context.

Although one might pass the name, it’s more convenient to avoid doing it, in this case the name would be assigned from the test name, which guarantees uniqueness of _FSM_s running in concurrent environment.

It should return the keyword which would be validated with NimbleOptions schema

  • :fsm (non-empty keyword/0) - Required. The FSM declaration to be used in tests.

    • :id (term/0) - The ID of the Finitomata tree. The default value is nil.

    • :implementation - Required. The implementatoin of Finitomata (the module with use Finitomata.)

    • :name (String.t/0) - The name of the Finitomata instance.

    • :payload (term/0) - Required. The initial payload for the FSM to start with.

    • :options (keyword/0) - Additional options to use in FSM initialization. The default value is [].

  • :context (keyword/0) - The additional context to be passed to actual ExUnit.Callbacks.setup/2 call.

Example:

describe "MyFSM tests" do
  setup_finitomata do
    parent = self()

    [
      fsm: [implementation: MyFSM, payload: %{}],
      context: [parent: parent]
    ]
  end

  
Link to this macro

test_path(test_name, ctx \\ quote do _ end, list)

View Source (macro)

Convenience macro to test the whole Finitomata path, from starting to ending state.

Must be used with a setup_finitomata/1 callback.

Example:

  test_path "The only path", %{finitomata: %{test_pid: parent}} do
    {:start, self()} ->
      assert_state :started do
        assert_payload do
          internals.counter ~> 1
          pid ~> ^parent
        end

        assert_receive {:on_start, ^parent}
      end

    :do ->
      assert_state :done do
        assert_receive :on_do
      end

      assert_state :* do
        assert_receive :on_end
      end
  end