17

I can't test a Reliable Service/Actor by just calling it's constructor and then test it's methods. var testService = new SomeService(); throws a NullReferenceException. So what can I do with deployed Service..

I understand that deployed SF Reliable Services/Actors are not standard .NET classes, and unit testing of deployed S/A maybe a strange idea.

Anyway now I'm trying to give it a try.

For example. I've just deployed a Service, than in the test I've created a Proxy object and added item into input queue of Service. Then I need to assert that input queue count = 1. And it works if I've just deployed a Service and no other Clients/Services/Actors have used it's input queue. But next time this test will be failed that's the problem. I need make the Service to stop operatating with other consumers, drop it's queue and than test it. For this purpose I can create some TestMode property and some methods like PropareoForTests/TestingCompleted and call them from test client before and after testing.

Is this is a bad idea to do it like that. Maybe are there some guidelines for unit testing SF? Thanks.

UPDATE:

While investigating Service Fabric Web Reference Application example I've found this TODO string:

/// TODO: Temporary property-injection for an IServiceProxyWrapper until constructor injection is available.

Does it mean that SF Services will improve it's DI support? What about actors?

AsValeO
  • 2,859
  • 3
  • 27
  • 64
  • 3
    Constructor dependency injection is actually available now in actors! When you register your actor type, you can also register a "factory" which is actually just a Func<> where you create your Actor class instance, which gives you control over your Actor's instantiation so you can inject dependencies through there. In services you can already do this, check out how we do it in the Party Cluster sample: https://github.com/Azure-Samples/service-fabric-dotnet-management-party-cluster/tree/master/PartyCluster/ClusterService – Vaclav Turecek Dec 03 '15 at 02:33
  • 1
    I wrote an answer on doing dependency injection with unity: http://stackoverflow.com/questions/30384780/azure-service-fabric-actor-dependency-injection/35900027#35900027 – Poul K. Sørensen Mar 09 '16 at 20:18
  • @VaclavTurecek your link is broken – Dismissile Jun 14 '16 at 14:26

2 Answers2

18

Actually you can test Reliable Services and Actors the same way you'd test any other class in .NET! They're only special in that they use certain hooks into the underlying platform, but other than that you can instantiate your service or actor class normally and call methods on it.

Currently, Reliable Services are a little easier to unit test because the primary hook into the platform, the State Manager, is an interface that's pluggable through the constructor.

For example, your service class might look like this:

EDIT: Updated with the GA release API (2.0.135)

class MyService : StatefulService
{
  public MyService (StatefulServiceContext context, IReliableStateManager stateManager)
      :base (context, stateManager)
  {
  }

  public void MyMethod()
  {
    // do stuff..
  }
}

Then you can test your service class like so:

[TestMethod]
public TestMyMethod()
{
  MockReliableStateManager stateManager = new MockReliableStateManager();
  MyService target = new MyService(stateManager);

  target.MyMethod();
  // validate results and all that good stuff
}

We have a full working example of actual services with lots of dependencies being unit tested available on GitHub: https://github.com/Azure-Samples/service-fabric-dotnet-management-party-cluster

This example has IReliableStateManager and IReliableDictionary mocks as well that you can use as a starting point for your own unit tests.

Vaclav Turecek
  • 9,020
  • 24
  • 29
  • 1
    Thanks, Vaclav! This is exactly what needed. Please take a look at the update. – AsValeO Nov 21 '15 at 12:10
  • 1
    In the RTM (Actors 2.0.135), the `Actor.StateManager` property is read-only (no `protected set`). How should I inject the state manager dependency for my actor class? – Lars Kemmann Apr 20 '16 at 00:36
  • And what about reminders and timers for actors? I can call the `ReceiveReminderAsync` method myself, but I need to verify the calls being made to `RegisterReminderAsync` (protected method on `ActorBase`). – Lars Kemmann Apr 23 '16 at 18:13
  • Seems we still cannot mock the state manager as Lars said. Any solutions for this? How do we unit test our actors if we cannot really see what they are doing with state? – Cleverguy25 Apr 28 '16 at 19:23
  • The Actor state manager is not injectable today, but we are working on making it so. For now you'll have to do something a little wonky like inject your own state management interface that wraps the state manager. – Vaclav Turecek May 02 '16 at 15:58
  • 1
    @VaclavTurecek: Can you provide an example of how you could "inject your own state management interface that wraps the state manager"? – Matt May 03 '16 at 15:15
  • I have similar situation and this solution helped me a lot. Thanks for that. I have one more condition where the method that I am testing is calling another micro service using ServiceProxy. How do I mock the service that the method I am testing using in it? – antar Jun 02 '16 at 21:16
  • I'm using the SeviceFabric.Mocks library: https://github.com/loekd/ServiceFabric.Mocks – Thieme Feb 20 '17 at 08:46
  • Your code would no longer compile after your edit. Can you update to show how you handle mocking of the StatefulServiceContext? Thanks – Taran Apr 29 '17 at 06:10
4

For mocking the state manager in Reliable Actors, you can do something like this:

private readonly IActorStateManager _stateManager;

public MyActor(IActorStateManager stateManager = null)
{
    _stateManager = stateManager ?? this.StateManager;
}

Actually, the StateManager isn't yet initialized at this time. We can get it when OnActivateAsync is called:

private IActorStateManager _stateManager;

// Unit tests can inject mock here.
public MyActor(IActorStateManager stateManager = null)
{
    _stateManager = stateManager;
}

protected override async Task OnActivateAsync()
{
    if (_stateManager == null)
    {
        _stateManager = StateManager;
    }
}

Just make sure to always use _stateManager in the rest of the code instead of this.StateManager.

Sander Molenkamp
  • 1,101
  • 7
  • 5