A scala.concurrent.ExecutionContext
implementation and a provider
of cats.effect.Timer
instances, that can simulate async boundaries
and time passage, useful for testing purposes.
Usage for simulating an ExecutionContext
):
implicit val ec = TestContext()
ec.execute(new Runnable { def run() = println("task1") })
ex.execute(new Runnable {
def run() = {
println("outer")
ec.execute(new Runnable {
def run() = println("inner")
})
}
})
// Nothing executes until `tick` gets called
ec.tick()
// Testing the resulting state
assert(ec.state.tasks.isEmpty)
assert(ec.state.lastReportedFailure == None)
Our TestContext
can also simulate time passage, as we are able
to builds a cats.effect.Timer
instance for any data type that
has a LiftIO
instance:
val ctx = TestContext()
val timer: Timer[IO] = ctx.timer[IO]
We can now simulate actual time:
val io = timer.sleep(10.seconds) *> IO(1 + 1)
val f = io.unsafeToFuture()
// This invariant holds true, because our IO is async
assert(f.value == None)
// Not yet completed, because this does not simulate time passing:
ctx.tick()
assert(f.value == None)
// Simulating time passing:
ctx.tick(10.seconds)
assert(f.value == Some(Success(2))
Simulating time makes this pretty useful for testing race conditions:
val never = IO.async[Int](_ => {})
val timeoutError = new TimeoutException
val timeout = timer.sleep(10.seconds) *> IO.raiseError[Int](timeoutError)
val pair = (never, timeout).parMapN(_ + _)
// Not yet
ctx.tick()
assert(f.value == None)
// Not yet
ctx.tick(5.seconds)
assert(f.value == None)
// Good to go:
ctx.tick(5.seconds)
assert(f.value, Some(Failure(timeoutError)))
- Companion
- object
Value members
Concrete methods
Derives a cats.effect.ContextShift
from this TestContext
, for any data
type that has an Async
instance.
Derives a cats.effect.ContextShift
from this TestContext
, for any data
type that has an Async
instance.
Example:
val ctx = TestContext()
// Building a Timer[IO] from this:
implicit val timer: Timer[IO] = ctx.timer[IO]
// Can now simulate time
val io = timer.sleep(10.seconds) *> IO(1 + 1)
val f = io.unsafeToFuture()
// This invariant holds true, because our IO is async
assert(f.value == None)
// Not yet completed, because this does not simulate time passing:
ctx.tick()
assert(f.value == None)
// Simulating time passing:
ctx.tick(10.seconds)
assert(f.value == Some(Success(2))
Inherited from ExecutionContext
, schedules a runnable
for execution.
Inherited from ExecutionContext
, schedules a runnable
for execution.
Derives a cats.effect.ContextShift
from this TestContext
for IO
.
Derives a cats.effect.ContextShift
from this TestContext
for IO
.
Derives a cats.effect.Timer
from this TestContext
for IO
.
Derives a cats.effect.Timer
from this TestContext
for IO
.
Inherited from ExecutionContext
, reports uncaught errors.
Inherited from ExecutionContext
, reports uncaught errors.
Returns the internal state of the TestContext
, useful for testing
that certain execution conditions have been met.
Returns the internal state of the TestContext
, useful for testing
that certain execution conditions have been met.
Triggers execution by going through the queue of scheduled tasks and executing them all, until no tasks remain in the queue to execute.
Triggers execution by going through the queue of scheduled tasks and executing them all, until no tasks remain in the queue to execute.
Order of execution isn't guaranteed, the queued Runnable
s are
being shuffled in order to simulate the needed non-determinism
that happens with multi-threading.
implicit val ec = TestContext()
val f = Future(1 + 1).flatMap(_ + 1)
// Execution is momentarily suspended in TestContext
assert(f.value == None)
// Simulating async execution:
ec.tick()
assert(f.value, Some(Success(2)))
The optional parameter can be used for simulating time, to be used in
combination with cats.effect.Timer
. See the
timer method.
Example:
val ctx = TestContext()
// Building a Timer[IO] from this:
implicit val timer: Timer[IO] = ctx.timer[IO]
// Can now simulate time
val io = timer.sleep(10.seconds) *> IO(1 + 1)
val f = io.unsafeToFuture()
// This invariant holds true, because our IO is async
assert(f.value == None)
// Not yet completed, because this does not simulate time passing:
ctx.tick()
assert(f.value == None)
// Simulating time passing:
ctx.tick(10.seconds)
assert(f.value == Some(Success(2))
- Value Params
- time
is an optional parameter for simulating time passing;
Executes just one tick, one task, from the internal queue, useful for testing that a some runnable will definitely be executed next.
Executes just one tick, one task, from the internal queue, useful for testing that a some runnable will definitely be executed next.
Returns a boolean indicating that tasks were available and that the head of the queue has been executed, so normally you have this equivalence:
while (ec.tickOne()) {}
// ... is equivalent with:
ec.tick()
Note that ask extraction has a random factor, the behavior being like tick, in order to simulate non-determinism. So you can't rely on some ordering of execution if multiple tasks are waiting execution.
- Returns
true
if a task was available in the internal queue, and was executed, orfalse
otherwise
Derives a cats.effect.Timer
from this TestContext
, for any data
type that has a LiftIO
instance.
Derives a cats.effect.Timer
from this TestContext
, for any data
type that has a LiftIO
instance.
Example:
val ctx = TestContext()
// Building a Timer[IO] from this:
implicit val timer: Timer[IO] = ctx.timer[IO]
// Can now simulate time
val io = timer.sleep(10.seconds) *> IO(1 + 1)
val f = io.unsafeToFuture()
// This invariant holds true, because our IO is async
assert(f.value == None)
// Not yet completed, because this does not simulate time passing:
ctx.tick()
assert(f.value == None)
// Simulating time passing:
ctx.tick(10.seconds)
assert(f.value == Some(Success(2))