I have a following flow using Akka Stream scaladsl
:
@testOnly
def restartableFlow: Flow[MyEvent, AmqpSendResult, NotUsed] = RestartFlow
.withBackoff(restartSettings) { () =>
Flow[MyEvent]
.mapAsyncUnordered(3)(acceptMessages) // uses an injected instance of MqClient returns a Future[AmqpSendResult]
.map {
case AmqpSendFailure(command, exception) =>
exception match {
case NonFatal(_) =>
sendToAmqp(command) // requeing of failed messages - out of scope of this question
throw exception
}
case AmqpSendSuccess => AmqpSendSuccess
}
}
For testing I am relying on Specs2
, ScalaMock
and akka.stream.testkit
. What I am trying to achieve with the test that is currently failing is this:
- two messages are being consumed (sent to) by the
restartableFlow
- On the first task
mqClient
is mocked to throw an error and so the inner (supervised flow) should be restarted - On the second task
mqClient
is mocked to successfully consume message - I am using a TestSink and its subscriber probe - I would expect an error first and than a
Next
However instead I get a timeout:
[error] ! deliver subsequent messages after an AMQP outage
[error] java.lang.AssertionError: assertion failed: timeout (3 seconds) during expectMsgClass waiting for class akka.stream.testkit.TestSubscriber$OnError (TestKit.scala:571)
[error] akka.testkit.TestKitBase.expectMsgClass_internal(TestKit.scala:571)
[error] akka.testkit.TestKitBase.expectMsgType(TestKit.scala:543)
[error] akka.testkit.TestKitBase.expectMsgType$(TestKit.scala:542)
[error] akka.testkit.TestKit.expectMsgType(TestKit.scala:973)
[error] akka.stream.testkit.TestSubscriber$ManualProbe.expectError(StreamTestKit.scala:486)
Finally here is the test:
"deliver subsequent messages after an AMQP outage" in
new ActorsTestSupport() with TestSetup {
// given a working restartableFlow from AmqpOutputService
val restartableFlow: Flow[MyEvent, AmqpSendResult, NotUsed] = getInstance(ConfigFactory.parseString("""
amqp {
restart-settings {
initial-timeout: 1ms
max-timeout: 10s
backoff-factor: 2
}
}
""")).restartableFlow
// and a MqClient that throws an error once
(mqClient
.sendCommandResponse[MyEvent](_: MyEvent))
.expects(sampleMessage)
.throwing(new RuntimeException())
// and that then it continues on successfully passing messages
(mqClient
.sendCommandResponse[MyEvent](_: MyEvent))
.expects(sampleMessage)
.returning()
.anyNumberOfTimes()
// when a message is sent to the flow
// the supervised stream should be restarted
val ((probe, _), sub) = TestSource[MyEvent]()
.via(restartableFlow)
.watchTermination() {
Keep.both
}
.toMat(testSink)(Keep.both) // testSink is just a TestSink[AmqpSendResult]()
.run()
sub.request(2)
probe.sendNext(sampleMessage)
sub.expectError()
probe.sendNext(sampleMessage)
sub.request(1)
sub.expectNext()
}
}
I don't think that the problem is with ActorTestSupport
or TestSetup
- I have two other (more straightforward) tests that work using the same setup.