Skip to content

Instantly share code, notes, and snippets.

@duongphuhiep
Created August 7, 2022 10:27
Show Gist options
  • Save duongphuhiep/15f223437bfd88a05de09c420473133b to your computer and use it in GitHub Desktop.
Save duongphuhiep/15f223437bfd88a05de09c420473133b to your computer and use it in GitHub Desktop.
Masstransit 8 TestHarness and Moq.Automocker
using MassTransit;
using MassTransit.Testing;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using Moq.AutoMock;
using System.Threading.Tasks;
using Xunit;
namespace HiepTest;
public record SubmitOrder(long Id, string CustomerName);
public record OrderSubmitted(string SubmitReference);
public interface INotifier
{
public Task<string> Notify(string content);
}
public interface IStockChecker { }
public interface IFidelityUpdater { }
public interface IPromotionManager { }
public class SubmitOrderConsumer : IConsumer<SubmitOrder>
{
private readonly INotifier notifier;
public SubmitOrderConsumer(INotifier notifier, IStockChecker stockChecker, IFidelityUpdater fidelityUpdater, IPromotionManager promotionManager)
{
this.notifier = notifier;
}
public async Task Consume(ConsumeContext<SubmitOrder> context)
{
var input = context.Message;
await notifier.Notify("Order submitted");
var output = new OrderSubmitted($"{input.Id}.{input.CustomerName}");
await context.RespondAsync(output);
}
}
public class SampleUnitTest
{
/// <summary>
/// Treat the consumer as a normal class and unit test it.
/// </summary>
[Fact]
public async Task NormalTest()
{
//ARRANGE
AutoMocker mocker = new AutoMocker();
var consumerUnderTest = mocker.CreateInstance<SubmitOrderConsumer>();
mocker.GetMock<INotifier>().Setup(m => m.Notify(It.IsAny<string>())).ReturnsAsync("OK");
var sampleInput = new SubmitOrder(1, "Hiep");
var consumeContextMock = mocker.GetMock<ConsumeContext<SubmitOrder>>();
consumeContextMock.SetupGet(x => x.Message).Returns(sampleInput);
//ACT
await consumerUnderTest.Consume(consumeContextMock.Object);
//ASSERT
//that the customer is notified "Order submitted"
mocker.GetMock<INotifier>().Verify(m => m.Notify(It.Is<string>(v => v == "Order submitted")));
//that the response is sent back, and it contains the customer's name
consumeContextMock.Verify(m => m.RespondAsync(It.Is<OrderSubmitted>(r => r.SubmitReference.Contains(sampleInput.CustomerName))));
}
[Fact]
public async Task UseTestHarness()
{
//ARRANGE
AutoMocker mocker = new AutoMocker();
var consumerUnderTest = mocker.CreateInstance<SubmitOrderConsumer>();
await using var provider = new ServiceCollection()
.AddMassTransitTestHarness(cfg =>
{
cfg.AddConsumer<SubmitOrderConsumer>();
})
.AddSingleton(consumerUnderTest)
.BuildServiceProvider(true);
var harness = provider.GetRequiredService<ITestHarness>();
await harness.Start();
var client = harness.GetRequestClient<SubmitOrder>();
var sampleInput = new SubmitOrder(1, "Hiep");
//ACT
var response = await client.GetResponse<OrderSubmitted>(sampleInput);
//ASSERT
//that the sampleInput is consumed (there exist a SubmitOrder in the ReceivedMessageList)
Assert.True(await harness.Consumed.Any<SubmitOrder>());
Assert.True(await harness.Consumed.Any<SubmitOrder>(x => ((SubmitOrder)x.MessageObject).Id == sampleInput.Id));
//that the SubmitOrderConsumer is invoked (in case there are multiple Consumer)
var consumerHarness = harness.GetConsumerHarness<SubmitOrderConsumer>();
Assert.True(await consumerHarness.Consumed.Any<SubmitOrder>());
Assert.True(await consumerHarness.Consumed.Any<SubmitOrder>(x => ((SubmitOrder)x.MessageObject).Id == sampleInput.Id));
//that a response is sent back in the harness (there exist a OrderSubmitted in the SentMessageList)
Assert.True(await harness.Sent.Any<OrderSubmitted>());
Assert.True(await harness.Sent.Any<OrderSubmitted>(x => ((OrderSubmitted)x.MessageObject).SubmitReference.Contains(sampleInput.CustomerName)));
//that the customer is notified "Order submitted"
mocker.GetMock<INotifier>().Verify(m => m.Notify(It.Is<string>(v => v == "Order submitted")));
//that the response contains the customer's name
Assert.Contains(sampleInput.CustomerName, response.Message.SubmitReference);
}
}
@duongphuhiep
Copy link
Author

We can extract the Harness Creation to a generic library

using MassTransit;
using MassTransit.Testing;
using Microsoft.Extensions.DependencyInjection;

namespace Moq.AutoMock.MassTransit
{
    /// <summary>
    /// AutoMocker & MassTransit Test Harness's integration
    /// </summary>
    public static class AutoMockerMassTransit
    {
        /// <summary>
        /// Start a harness to test a single consumer. The consumer instance will be created with AutoMocker.
        /// Then you can just use this AutoMocker instance to access to the consumer instance (mocker.Get) or any of its Mock dependencies (mocker.GetMock)
        /// The returned test harness is started, so it is ready to be used as in the [MassTransit Documentation](https://masstransit-project.com/usage/testing.html)
        /// </summary>
        /// <typeparam name="T">type of the Consumer</typeparam>
        /// <param name="mocker">the AutoMocker instance</param>
        /// <returns>MassTransit Test Harness</returns>
        public static async Task<ITestHarness> StartTestHarnessFor<T>(this AutoMocker mocker) where T : class, IConsumer
        {
            var consumerUnderTest = mocker.CreateInstance<T>();
            return await StartTestHarnessFor(consumerUnderTest);
        }

        /// <summary>
        /// Start a harness to test a single consumer instance.
        /// The returned test harness is started, so it is ready to be used as in the [MassTransit Documentation](https://masstransit-project.com/usage/testing.html)
        /// </summary>
        /// <typeparam name="T">type of the Consumer</typeparam>
        /// <param name="consumer">the Consumer Under Test instance</param>
        /// <returns>MassTransit Test Harness</returns>
        public static async Task<ITestHarness> StartTestHarnessFor<T>(T consumer) where T : class, IConsumer
        {
            var provider = new ServiceCollection()
                .AddMassTransitTestHarness(cfg =>
                {
                    cfg.AddConsumer<T>();
                })
                .AddSingleton(consumer)
                .BuildServiceProvider(true);

            var harness = provider.GetRequiredService<ITestHarness>();
            await harness.Start();
            return harness;
        }
    }
}

Usage:

So the above codes of the UseTestHarness test is reduced to

// Arrange

AutoMocker mocker = new AutoMocker();
var harness = await mocker.StartTestHarnessFor<SubmitOrderConsumer>();
var client = harness.GetRequestClient<SubmitOrder>();
var sampleInput = new SubmitOrder(1, "Hiep");

//Act
var response = await client.GetResponse<OrderSubmitted>(sampleInput);

//ASSERT
...

@duongphuhiep
Copy link
Author

duongphuhiep commented Aug 8, 2022

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment