Integration Tests in Service Fabric – Write tests accessing infrastructure

Previously I explained how the test definition works. In this post I’ll show how we declare tests and get access to the infrastructure provided by the stateful service. Components that are used inside a stateful service most likely end-up using either the StatefulServiceContext, the IStatefulServicePartition, the IReliableStateManager or other methods and properties provided by the stateful service inheritance hierarchy. Let’s have an oversimplified look at how the hierarchy looks like:

public abstract class StatefulServiceBase : IStatefulUserServiceReplica
  {
    public StatefulServiceContext Context { get; }

    protected IStatefulServicePartition Partition { get; }

    // more not of interest
  }

public abstract class StatefulService : StatefulServiceBase {

    public IReliableStateManager StateManager { get; }

    // more not of interest
}

So any component that needs to for example use the IReliableStateManager inject the state manager. This doesn’t require magic like Inversion of Control containers since the stateful service declaration or the endpoint communication listeners provided by it should be the composition root of the infrastructure used by the service. Below is a short, simplified and contrived example how such a code might look like.

sealed class OrderShippingService : StatefulService {
    protected override Task RunAsync(CancellationToken cancellationToken) {
        var orderShipping = new OrderShipping(StateManager);

        while(!cancellationToken.IsCancellationRequested) { 
            await orderShipping.ShipIt(cancellationToken).ConfigureAwait(false); 
        }
    }
}

class OrderShipping {
    public OrderShipping(IReliableStateManager stateManager) {
        this.stateManager = stateManager;
    }

    public async Task ShipIt(CancellationToken token) {
        var dictionary = await stateManager
            .GetOrAddAsync<IReliableDictionary<string, long>>("ordersShipped", TimeSpan.FromSeconds(5))
            .ConfigureAwait(false);

        using(var tx = stateManager.CreateTransaction()) 
        {
            long count = await wordCountDictionary
                .AddOrUpdateAsync(tx, "FancyJackets", 1, (key, oldValue) => oldValue + 1)
                .ConfigureAwait(false);

            await tx.CommitAsync().ConfigureAwait(false);
        }
    }
}

The component does nothing fancy here. I gets access to the reliable dictionary called ordersShipped and transactionally increments the counter under the key FancyJackets. To test this component with an integration test it is now possible to write the following code.

[TestFixture]
public class OrderShippingTests : INeed<IReliableStateManager> {
    IReliableStateManager stateManager;

    [Test]
    public async Task Should_increment_the_counter() {
        // Arrange
        var testee = new OrderShipping(stateManager);

        // Act
        await testee.ShipIt(CancellationToken.None)
            .ConfigureAwait(false);

        // Assert
        var dictionary = await stateManager.GetOrAddAsync<IReliableDictionary<string, long>>("ordersShipped", TimeSpan.FromSeconds(5));
        // .. you'll get the point
    }

    public void Need(IReliableStateManager dependency) {
        stateManager = dependency;
    }
}

As the example test code above shows with the Service Fabric testing approach outlined in my series wheenver a test needs access to the reliable state manager, the stateful service context or the partition information it can implement the `INeed` property. A test needing all three of those dependencies would look like the following:

public class TestNeedingAllDependencies: INeed<IStatefulServicePartition>, INeed<StatefulServiceContext>, INeed<IReliableStateManager>
{
        public void Need(IReliableStateManager dependency) => stateManager = dependency;
        public void Need(IStatefulServicePartition dependency) => servicePartition = dependency;
        public void Need(StatefulServiceContext dependency) => serviceContext = dependency;
}

Or if we prefer to use the OrderShippingService as an entry point for the tests we can implement `INeed` like below

public class TestNeedingAllDependencies: INeed<OrderShippingService>
{
        public void Need(OrderShippingService dependency) => shippingService = dependency;
}

In the next post I’m going to walk you through the infrastructure bits and pieces that make this possible. Stay tuned.

About the author

Daniel Marbach

1 comment

Recent Posts