Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
This tutorial shows how to unit test your grains to ensure they behave correctly. There are two main ways to unit test your grains, and the method you choose depends on the type of functionality you're testing. Use the Microsoft.Orleans.TestingHost NuGet package to create test silos for your grains, or use a mocking framework like Moq to mock parts of the Orleans runtime your grain interacts with.
Use the TestCluster
The Microsoft.Orleans.TestingHost
NuGet package contains TestCluster, which you can use to create an in-memory cluster (comprised of two silos by default) for testing grains.
using Orleans.TestingHost;
namespace Tests;
public class HelloGrainTests
{
[Fact]
public async Task SaysHelloCorrectly()
{
var builder = new TestClusterBuilder();
var cluster = builder.Build();
cluster.Deploy();
var hello = cluster.GrainFactory.GetGrain<IHelloGrain>(Guid.NewGuid());
var greeting = await hello.SayHello("World");
cluster.StopAllSilos();
Assert.Equal("Hello, World!", greeting);
}
}
Due to the overhead of starting an in-memory cluster, you might want to create a TestCluster
and reuse it among multiple test cases. For example, achieve this using xUnit's class or collection fixtures.
To share a TestCluster
between multiple test cases, first create a fixture type:
using Orleans.TestingHost;
public sealed class ClusterFixture : IDisposable
{
public TestCluster Cluster { get; } = new TestClusterBuilder().Build();
public ClusterFixture() => Cluster.Deploy();
void IDisposable.Dispose() => Cluster.StopAllSilos();
}
Next, create a collection fixture:
[CollectionDefinition(Name)]
public sealed class ClusterCollection : ICollectionFixture<ClusterFixture>
{
public const string Name = nameof(ClusterCollection);
}
You can now reuse a TestCluster
in your test cases:
using Orleans.TestingHost;
namespace Tests;
[Collection(ClusterCollection.Name)]
public class HelloGrainTestsWithFixture(ClusterFixture fixture)
{
private readonly TestCluster _cluster = fixture.Cluster;
[Fact]
public async Task SaysHelloCorrectly()
{
var hello = _cluster.GrainFactory.GetGrain<IHelloGrain>(Guid.NewGuid());
var greeting = await hello.SayHello("World");
Assert.Equal("Hello, World!", greeting);
}
}
When all tests complete and the in-memory cluster silos stop, xUnit calls the Dispose() method of the ClusterFixture
type. TestCluster
also has a constructor accepting TestClusterOptions that you can use to configure the silos in the cluster.
If you use Dependency Injection in your Silo to make services available to Grains, you can use this pattern as well:
using Microsoft.Extensions.DependencyInjection;
using Orleans.TestingHost;
namespace Tests;
public sealed class ClusterFixtureWithConfig : IDisposable
{
public TestCluster Cluster { get; } = new TestClusterBuilder()
.AddSiloBuilderConfigurator<TestSiloConfigurations>()
.Build();
public ClusterFixtureWithConfig() => Cluster.Deploy();
void IDisposable.Dispose() => Cluster.StopAllSilos();
}
file sealed class TestSiloConfigurations : ISiloConfigurator
{
public void Configure(ISiloBuilder siloBuilder)
{
siloBuilder.ConfigureServices(static services =>
{
// TODO: Call required service registrations here.
// services.AddSingleton<T, Impl>(/* ... */);
});
}
}
Use mocks
Orleans also allows mocking many parts of the system. For many scenarios, this is the easiest way to unit test grains. This approach has limitations (e.g., around scheduling reentrancy and serialization) and might require grains to include code used only by your unit tests. The Orleans TestKit provides an alternative approach that sidesteps many of these limitations.
For example, imagine the grain you're testing interacts with other grains. To mock those other grains, you also need to mock the GrainFactory member of the grain under test. By default, GrainFactory
is a normal protected
property, but most mocking frameworks require properties to be public
and virtual
to enable mocking. So, the first step is to make GrainFactory
both public
and virtual
:
public new virtual IGrainFactory GrainFactory
{
get => base.GrainFactory;
}
Now you can create your grain outside the Orleans runtime and use mocking to control the behavior of GrainFactory
:
using Xunit;
using Moq;
namespace Tests;
public class WorkerGrainTests
{
[Fact]
public async Task RecordsMessageInJournal()
{
var data = "Hello, World";
var journal = new Mock<IJournalGrain>();
var worker = new Mock<WorkerGrain>();
worker
.Setup(x => x.GrainFactory.GetGrain<IJournalGrain>(It.IsAny<Guid>()))
.Returns(journal.Object);
await worker.DoWork(data)
journal.Verify(x => x.Record(data), Times.Once());
}
}
Here, create the grain under test, WorkerGrain
, using Moq. This allows overriding the GrainFactory
's behavior so it returns a mocked IJournalGrain
. You can then verify that WorkerGrain
interacts with IJournalGrain
as expected.