Yesterday I blogged about our approach to test Exceptions thrown by your Akka.NET actors. Today I want to talk about an issue we encountered when using our own ActorSystem.
In the example yesterday we were using the Sys property of the Akka.NET Testkit which gives us access to a built-in Actor System created for us:
var actor = Sys.ActorOf(Props.Create(() => new SampleActor()));
var filter = CreateEventFilter(Sys);
However when we tried to run the same test but using our own ActorSystem instance instead the test failed and no Exception was received by the EventFilter.
[TestFixture] | |
public class SampleActorTests : TestKit | |
{ | |
[Test] | |
public void SampleActor_Should_Throw_An_Exception() | |
{ | |
//Arrange | |
//We are using our own actorsystem | |
var actorSystem=new ActorSystem(); | |
var actor = actorSystem.ActorOf(Props.Create(() => new SampleActor())); | |
var filter = CreateEventFilter(actorSystem); | |
//Act & Assert | |
//This test fails :-( --> No exception is received | |
filter.Exception<Exception>().Expect(1,TimeSpan.FromSeconds(3), () => { actor.Tell(new SampleActor.Message()); }); | |
} | |
} |
To understand why this fails, you have to understand what TestKit is doing behind the scenes when creating an ActorSystem for you. For every test you run it creates a new ActorSystem instance using the following configuration:
###################################### | |
# Akka Testkit Reference Config File # | |
###################################### | |
# This is the reference config file that contains all the default settings. | |
akka { | |
# Replace the default value, ["Akka.Event.DefaultLogger"], with TestEventListener | |
# This logger behaves exactly like DefaultLogger, but makes it possible | |
# to use EventFiltering. If no filter is specified it logs to StdOut just like | |
# DefaultLogger. | |
loggers = ["Akka.TestKit.TestEventListener, Akka.TestKit"] | |
suppress-json-serializer-warning = true | |
test { | |
# factor by which to scale timeouts during tests, e.g. to account for shared | |
# build system load | |
timefactor = 1.0 | |
# duration of EventFilter.intercept waits after the block is finished until | |
# all required messages are received | |
filter-leeway = 3s | |
# duration to wait in expectMsg and friends outside of within() block | |
# by default | |
single-expect-default = 3s | |
# The timeout that is added as an implicit by DefaultTimeout trait | |
# This is used for Ask-pattern | |
default-timeout = 5s | |
calling-thread-dispatcher { | |
type = "Akka.TestKit.CallingThreadDispatcherConfigurator, Akka.TestKit" | |
throughput = 2147483647 | |
} | |
test-actor.dispatcher { | |
type = "Akka.TestKit.CallingThreadDispatcherConfigurator, Akka.TestKit" | |
throughput = 2147483647 | |
} | |
} | |
} | |
Additionally, it's also possible to change the default IScheduler implementation in the Akka.TestKit to use a virtualized TestScheduler implementation that Akka.NET developers can use to artificially advance time forward. To swap in the TestScheduler, developers will want to include the HOCON below: | |
###################################### | |
# Akka Testkit Reference Config File # | |
###################################### | |
# This is the reference config file that contains all the default settings. | |
akka { | |
# Replace the default value, ["Akka.Event.DefaultLogger"], with TestEventListener | |
# This logger behaves exactly like DefaultLogger, but makes it possible | |
# to use EventFiltering. If no filter is specified it logs to StdOut just like | |
# DefaultLogger. | |
loggers = ["Akka.TestKit.TestEventListener, Akka.TestKit"] | |
scheduler { | |
implementation = "Akka.TestKit.TestScheduler, Akka.TestKit" | |
} | |
test { | |
# factor by which to scale timeouts during tests, e.g. to account for shared | |
# build system load | |
timefactor = 1.0 | |
# duration of EventFilter.intercept waits after the block is finished until | |
# all required messages are received | |
filter-leeway = 3s | |
# duration to wait in expectMsg and friends outside of within() block | |
# by default | |
single-expect-default = 3s | |
# The timeout that is added as an implicit by DefaultTimeout trait | |
# This is used for Ask-pattern | |
default-timeout = 5s | |
calling-thread-dispatcher { | |
type = "Akka.TestKit.CallingThreadDispatcherConfigurator, Akka.TestKit" | |
throughput = 2147483647 | |
} | |
test-actor.dispatcher { | |
type = "Akka.TestKit.CallingThreadDispatcherConfigurator, Akka.TestKit" | |
throughput = 2147483647 | |
} | |
} | |
} |
The important line here is the following:
loggers = ["Akka.TestKit.TestEventListener, Akka.TestKit"]
This line will link the TestKit EventFilter to the ActorSystem. As we didn’t had any configuration setup for our own ActorSystem, the EventFilter didn’t pick up any event.
To fix it, we included the configuration above in our app.config:
<?xml version="1.0" encoding="utf-8"?> | |
<configuration> | |
<configSections> | |
<section name="akka" type="Akka.Configuration.Hocon.AkkaConfigurationSection, Akka" /> | |
</configSections> | |
<akka> | |
<hocon> | |
<![CDATA[ | |
###################################### | |
# Akka Testkit Reference Config File # | |
###################################### | |
# This is the reference config file that contains all the default settings. | |
akka { | |
# Replace the default value, ["Akka.Event.DefaultLogger"], with TestEventListener | |
# This logger behaves exactly like DefaultLogger, but makes it possible | |
# to use EventFiltering. If no filter is specified it logs to StdOut just like | |
# DefaultLogger. | |
loggers = ["Akka.TestKit.TestEventListener, Akka.TestKit"] | |
suppress-json-serializer-warning = true | |
test { | |
# factor by which to scale timeouts during tests, e.g. to account for shared | |
# build system load | |
timefactor = 1.0 | |
# duration of EventFilter.intercept waits after the block is finished until | |
# all required messages are received | |
filter-leeway = 3s | |
# duration to wait in expectMsg and friends outside of within() block | |
# by default | |
single-expect-default = 3s | |
# The timeout that is added as an implicit by DefaultTimeout trait | |
# This is used for Ask-pattern | |
default-timeout = 5s | |
calling-thread-dispatcher { | |
type = "Akka.TestKit.CallingThreadDispatcherConfigurator, Akka.TestKit" | |
throughput = 2147483647 | |
} | |
test-actor.dispatcher { | |
type = "Akka.TestKit.CallingThreadDispatcherConfigurator, Akka.TestKit" | |
throughput = 2147483647 | |
} | |
} | |
} | |
]]> | |
</hocon> | |
</akka> | |
<runtime> | |
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> | |
<dependentAssembly> | |
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" /> | |
<bindingRedirect oldVersion="0.0.0.0-10.0.0.0" newVersion="10.0.0.0" /> | |
</dependentAssembly> | |
<dependentAssembly> | |
<assemblyIdentity name="Iesi.Collections" publicKeyToken="aa95f207798dfdb4" culture="neutral" /> | |
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0" /> | |
</dependentAssembly> | |
<dependentAssembly> | |
<assemblyIdentity name="nunit.framework" publicKeyToken="2638cd05610744eb" culture="neutral" /> | |
<bindingRedirect oldVersion="0.0.0.0-3.9.0.0" newVersion="3.9.0.0" /> | |
</dependentAssembly> | |
</assemblyBinding> | |
</runtime> | |
</configuration> |