This release includes extensive changes to the testing API. It formalizes the concepts of "actions" and "expectations" with the goal of a more consistent API and a greater level of extensibility.
An action is an operation performed within a test that causes the Dogma
application being tested to do something. They are represented by the Action
interface.
The following functions in the testkit
package each return an Action
:
ExecuteCommand()
RecordEvent()
AdvanceTime()
Call()
Prior to this release the Test
type had methods with these names. These
methods have been removed. Instead, the Test.Expect()
method is used to
perform a single action and fails the test unless it meets a specific
expectation.
Actions can be performed without any expectations using Test.Prepare()
.
An expectation is some criteria that an action is expected to meet. They are
represented by the Expectation
interface.
The following functions in the testkit
package each return an Expectation
:
ToExecuteCommand()
ToRecordEvent()
ToExecuteCommandOfType()
ToRecordEventOfType()
AllOf()
AnyOf()
NoneOf()
ToSatisfy()
Prior to this release expectations were called "assertions". These functions
(some with slightly different names) were in the assert
package, which has
been removed.
If you are migrating tests from testkit v0.10.0 or prior you will need to accomodate the changes listed in this section.
Example code is provided showing how each feature was used both before and after
this release. Within these examples the test
identifier refers to a
testkit.Test
variable. It is assumed that the testkit
package has been "dot
imported" meaning that any unqualified function call refers to a function in the
testkit
package.
Prior to this release a Test
was constructed by first constructing a Runner
then calling its Begin()
or BeginContext()
method. The Runner
type has
been removed and its methods have been moved to functions in the testkit
package.
Before | After |
test := New(app).Begin(t) |
test := Begin(t, app) |
test := New(app).BeginContext(ctx, t) |
test := BeginContext(ctx, t, app) |
Prior to this release Prepare()
accepted dogma.Message
parameters. It now
requires Action
values instead.
This change allows any action to be performed without an expectation. This is
particularly useful with AdvanceTime()
which was often used with the special
assert.Nothing
assertion to advance the test's virtual clock without making
any real assertion.
Before | After |
test.Prepare(
SomeCommand{ /* ... */ },
SomeEvent{ /* ... */ },
) |
test.Prepare(
ExecuteCommand(SomeCommand{ /* ... */ }),
RecordEvent(SomeEvent{ /* ... */ }),
) |
test.AdvanceTime(
ByDuration(10 * time.Second),
assert.Nothing,
) |
test.Prepare(
AdvanceTime(ByDuration(10 * time.Second)),
) |
These methods on Test
have been replaced with functions of the same name, each
of which returns an Action
.
This change allows these actions to be performed without an expectation using
Test.Prepare()
.
Before | After |
test.ExecuteCommand(
SomeCommand{ /* ... */ },
assert.EventRecorded(SomeEvent{ /* ... */ }),
) |
test.Expect(
ExecuteCommand(SomeCommand{ /* ... */ }),
ToRecordEvent(SomeEvent{ /* ... */ }),
) |
test.RecordEvent(
SomeEvent{ /* ... */ },
assert.CommandExecuted(SomeCommand{ /* ... */ }),
) |
test.Expect(
RecordEvent(SomeEvent{ /* ... */ }),
ToExecuteCommand(SomeCommand{ /* ... */ }),
) |
test.AdvanceTime(
ByDuration(10 * time.Second),
assert.EventRecorded(SomeEvent{ /* ... */ }),
) |
test.Expect(
AdvanceTime(ByDuration(10 * time.Second)),
ToRecordEvent(SomeEvent{ /* ... */ }),
) |
test.Call(
func() error { return nil },
assert.EventRecorded(SomeEvent{ /* ... */ }),
) |
test.Expect(
Call(func() { /* no error is returned ¹ */ }),
ToRecordEvent(SomeEvent{ /* ... */ }),
) |
The commonly used assertions that were in the assert
package have been
reimplemented in testkit
as expectations. Some of the function names have been
changed.
The assert.Nothing
assertion has been removed. It is no longer necessary as
any action can be performed without an expectation using Test.Prepare()
.
Before | After |
assert.EventRecorded(SomeEvent{ /* ... */ }) |
ToRecordEvent(SomeEvent{ /* ... */ }) |
assert.EventTypeRecorded(SomeEvent{}) |
ToRecordEventOfType(SomeEvent{}) |
assert.CommandExecuted(SomeCommand{ /* ... */ }) |
ToExecuteCommand(SomeCommand{ /* ... */ }) |
assert.CommandTypeExecuted(SomeCommand{}) |
ToExecuteCommandOfType(SomeCommand{}) |
assert.AllOf(/* ... */) |
AllOf(/* ... */) |
assert.AnyOf(/* ... */) |
AnyOf(/* ... */) |
assert.NoneOf(/* ... */) |
NoneOf(/* ... */) |
assert.Should(
"do something",
func(s *assert.S) { /* ... */ },
) |
ToSatisfy(
"do something",
func(t *SatisfyT) { /* ... */ },
) |
Within a Test
both projection and integration message handler types are
disabled by default. Prior to this release such handlers could be enabled using
WithOperationOptions(...)
.
As of this release enabling or disabling handlers by type is discouraged ².
Instead, individual handlers are enabled and disabled by name using the
Test.EnableHandlers()
and DisableHandlers()
methods.
This change is made to accomodate future changes to the expectation and reporting systems that will analyse each handler's routing configuration to eliminate impossible expectations and provide more meaningful failure reports.
Before | After |
test := New().Begin(
t,
WithOperationOptions(
engine.EnableProjections(true),
),
) |
test := Begin(t).
EnableHandlers("some-projection") |
¹ The function passed to Call()
no longer returns an error
. Use the standard
features of your testing framework to ensure no errors occur within the
function.
² For the time being it is still possible to set engine operation options within
a Test
using WithUnsafeOperationOptions()
. This approach provides no
guarantees as to how these options will interact with the operation options that
are set automatically by the Test
.