When working with dates or time we often have the problem of writing stable tests.
Java only provides a FixedClock for testing.
However, often time related code has to deal with the change of time,
so a fixed clock is not enough or makes the test harder to follow.
The prerequisite for using both FixedClock and Spocks MutableClock is that the production code,
actually uses a configurable Clock and not just the parameterless Instant.now()
or the corresponding methods in the other java.time.* classes.
link:{sourcedir}/utilities/AgeFilter.java[role=include]-
Clockis injected via constructor -
Clockis used to get the current date
link:{sourcedir}/utilities/MutableClockDocSpec.groovy[role=include]-
MutableClockcreated with a well known time -
Clockis injected via constructor -
ageis less than18so the result isfalse -
the clock is advanced by one day
-
ageis equal to18so the result istrue
There are many more ways to modify MutableClock just have a look at the JavaDocs, or the test code spock.util.time.MutableClockSpec.
The utility class AsyncConditions can be helpful when working with asynchronous assertions. These are assertions made
from a different thread than the one running the test, for example when working with callback functions.
The individual assertions are collected using the evaluate method, and in the asserting block, e.g. then, the
await method is called to verify the results, blocking until a timeout expires, and failing if any of the evaluations
failed.
The timeout in seconds can be specified as a parameter, see the example below. The default is 1.0 seconds.
Additionally, the number of expected evaluations must be provided initially, the default value is 1.
|
Note
|
In order to use implicit assertions, a typed instance of this class must be declared. When declaring an untyped
instance (using the def keyword), it is required to explicitly assert all statements inside the block (see example
below).
|
link:{sourcedir}/utilities/concurrent/AsyncConditionsDocSpec.groovy[role=include]-
create a default
AsyncConditionsobject (expecting a single evaluation) -
A new thread is created, and code is passed to be run from it. This could also happen implicitly, when working with a method expecting a callback parameter.
-
Pass any code that wants to do assertions to the
evaluatemethod. Because we declared the instance ofAsyncConditionsusing thedefkeyword, we have to useassertexplicitly. -
finally, call the
awaitmethod -
Create an
AsyncConditionsobject (expecting three evaluations). This time we are using a statically typed instance. -
Call
evaluatemultiple times. Using a typed instance above allows using implicit assertions (noassertkeyword). keyword. -
call
awaitin the end, specifying 2.5 seconds as the timeout
The PollingConditions utility can be used to (repeatedly) check on asynchronous conditions. The difference compared to
AsyncConditions is that the result is obtained passively, there is no await method.
PollingConditions allows to configure the timeout, delay and a delay increasing factor for the checks. Any number of
code blocks are passed to the eventually method, which will evaluate them, according to given time parameters, and
either pass or throw one of the appropriate Spock exceptions.
The within method can be used to override the timeout for a single invocation. See the second example below for a more
complicated use case.
|
Note
|
The same restriction when using the def keyword exists here also, see the note on AsyncConditions
|
link:{sourcedir}/utilities/concurrent/PollingConditionsDocSpec.groovy[role=include]-
create a
PollingConditionsobject -
values are set to variables in an asynchronous way
-
block(s) of code containing assertions, passed in a
whenstep -
verify the result by checking for exceptions
-
Override the timeout for a single invocation. Using
expect(orthen) here forces immediate evaluation. -
only the first failed evaluation will be reported
The two utility classes BlockingVariable and BlockingVariables are there to help with collecting variables that are
written asynchronously, e.g. from another thread. Reading the variable(s) will block the current thread until a value is
available and returned or the timeout is reached.
For a single variable, a type parameter can be provided, see the example below.
An instance of BlockingVariables allows setting properties to it dynamically, and comparisons (i.e. assertions) can be
made for the individual properties, accessing them by their name.
The maximum amount of seconds to wait for can be specified as a parameter, the default is 1.0 seconds.
link:{sourcedir}/utilities/concurrent/BlockingVariablesDocSpec.groovy[role=include]-
create a
BlockingVariableobject, providing an optional type parameter -
A new thread is created, and immediately put to sleep for some time. Afterwards it will set the value.
-
The value is accessed via the
getmethod. This happens immediately, as the main thread is never interrupted. The method will block until the value is available. -
create a
BlockingVariablesobject for multiple variables, providing an optional timeout -
set values for
foo,barandbazat some point in the future -
compare the individual properties, blocking this thread until they are available