In this repo is examples of using AutoFixture and AutoMoq in different scenarios.
Given a simple class:
public class Calculator
{
public int Multiply(byte lhs, byte rhs) => lhs * rhs;
}A test might look like:
[Fact]
public void WHEN_3_multiply_5_THEN_0()
{
var sut = new Calculator();
var result = sut.Multiply(3, 5);
result.Should().Be(15);
}Or to cover more scenarios.
[Theory]
[InlineData(0, 0, 0)]
[InlineData(0, 1, 0)]
[InlineData(1, 1, 1)]
[InlineData(2, 2, 4)]
[InlineData(3, 5, 15)]
public void WHEN_x_multiply_y_THEN_z(byte x, byte y, int expectedProduct)
{
var sut = new Calculator();
var product = sut.Multiply(x, y);
product.Should().Be(expectedProduct);
}Constructing the var sut = new Calculator() in every test can get pretty repeatitive. So:
- make the
suta property of the test class - move initialisation to the test class constructor
Now the
sutis shared across all tests.
Or just ask AutoMoq to create one for us in the scope of each test:
[Theory]
[AutoMoqData]
public void WHEN_3_multiply_5_THEN_0(Calculator sut)
{
var result = sut.Multiply(3, 5);
result.Should().Be(15);
}note: with AutoFixture / AutoMoq attribute tests as [Theory] (not [Fact])
When values for specific scenarios are provided, these are populated in the order of the test function parameters. The remainder of the parameters are automatically created.
[Theory]
[InlineAutoMoqData(0, 0, 0)]
[InlineAutoMoqData(0, 1, 0)]
[InlineAutoMoqData(1, 1, 1)]
[InlineAutoMoqData(2, 2, 4)]
[InlineAutoMoqData(3, 5, 15)]
public void WHEN_x_multiply_y_THEN_z(
byte x,
byte y,
int expectedProduct,
Calculator sut)
{
var result = sut.Multiply(x, y);
result.Should().Be(expectedProduct);
}If the individual scenario values don't matter but some valid values are required each time the test runs:
[Theory]
[AutoMoqData]
public void WHEN_multiply_THEN_result_is_not_less_than_factors(byte x, byte y, Calculator sut)
{
var result = sut.Multiply(x, y);
result.Should().BeGreaterOrEqualTo(x);
result.Should().BeGreaterOrEqualTo(y);
}note: different values for x and y will be created each time the test runs
To configure the [AutoMoq] and [InlineAutoMoqData] attributes for these basic scenarios requires some declarations. Put them somewhere accessible to all your tests:
public class AutoMoqDataAttribute : AutoDataAttribute
{
public AutoMoqDataAttribute() : base(() => new Fixture()) { }
}
public class InlineAutoMoqDataAttribute : InlineAutoDataAttribute
{
public InlineAutoMoqDataAttribute(params object[] objects) : base(new AutoMoqDataAttribute(), objects) { }
}Given a class (and friends):
public interface ITaxAuditor
{
void AuditTaxCalculation(string orderNumber, decimal orderSubtotal, decimal taxRate, decimal tax);
}
public class FixedRateTaxCalculator
{
const decimal TaxRate = 0.1m;
private readonly ITaxAuditor _taxAuditor;
public FixedRateTaxCalculator(ITaxAuditor taxAuditor) => _taxAuditor = taxAuditor;
public decimal CalculateForOrder(string orderNumber, decimal orderSubtotal)
{
var taxAmount = orderSubtotal * TaxRate;
_taxAuditor.AuditTaxCalculation(orderNumber, orderSubtotal, TaxRate, taxAmount);
return taxAmount;
}
}First AutoFixture needs to learn how to AutoMoq:
- public AutoMoqDataAttribute() : base(() => new Fixture()) { }
+ public AutoMoqDataAttribute() : base(() =>
+ {
+ var fixture = new Fixture();
+ fixture.Customize(new AutoMoqCustomization());
+ return fixture;
+ }) { }Now a test:
[Theory]
[InlineAutoMoqData(0, 0)]
[InlineAutoMoqData(0.5, 0.05)]
[InlineAutoMoqData(1, 0.1)]
[InlineAutoMoqData(50, 5)]
public void WHEN_calculate_THEN_tax_is_10_percent(
decimal orderSubtotal,
decimal expectedTaxAmount,
string orderNumber,
[Frozen] Mock<ITaxAuditor> accountingSystemMock,
FixedRateTaxCalculator sut)
{
sut.CalculateForOrder(orderNumber, orderSubtotal);
accountingSystemMock
.Verify(mock => mock.AuditTaxCalculation(orderNumber, orderSubtotal, 0.1m, expectedTaxAmount));
}What is going on here?
- the values for
orderSubtotalandexpectedTaxAmountare populated from theInlineAutoMoqDataattribute parameters - the remaining values are automatically generated by
AutoFixture/AutoMoq- including theMock<ITaxAuditor> accountingSystemMockparameter. - what's
[Frozen]? For each requirement of the test method and its dependencies,AutoFixture/AutoMoqwill create a new instance of what ever type is required. But for theMock<ITaxAuditor>the sameMoqinstance needs to be both injected into theFixedRateTaxCalculatorand made available to the test method for setup/verify.
Therefore in most tests Mock<> parameters will need to be attributed [Frozen].
note: Mocks are built and injected into constructors of instances that require them before the test method code runs - so no Setup() calls can be configured to match in constructors. Verify() calls can be made for invocations in the constructor - as the mock will record these
Sometimes it is necessary to help AutoFixture / AutoMoq create values because the auto generated values aren't helpful for the situation.
Maybe particular orders get a preferential tax rate.
public class PreferentialTaxRateCalculator
{
private const decimal DefaultTaxRate = 0.1m;
private readonly IReadOnlyDictionary<string, decimal> _preferentialOrderRates;
private readonly ITaxAuditor _taxAuditor;
public PreferentialTaxRateCalculator(ITaxAuditor taxAuditor, IReadOnlyDictionary<string, decimal> preferentialOrderRatesTable)
{
_taxAuditor = taxAuditor;
_preferentialOrderRates = preferentialOrderRatesTable;
}
public decimal CalculateForOrder(string orderNumber, decimal orderSubtotal)
{
var taxRate = _preferentialOrderRates.TryGetValue(orderNumber, out var preferentialRate) ? preferentialRate : DefaultTaxRate;
var taxAmount = orderSubtotal * taxRate;
_taxAuditor.AuditTaxCalculation(orderNumber, orderSubtotal, taxRate, taxAmount);
return taxAmount;
}
}And a test
[Theory]
[InlineAutoMoqData("123456", 10, 0.05, 0.5)]
[InlineAutoMoqData("654321", 10, 0.1, 1)]
public void GIVEN_preferential_rates_configured_WHEN_calculate_THEN_tax_is_5_percent(
string orderNumber,
decimal orderSubtotal,
decimal expectedTaxRate,
decimal expectedTaxAmount,
[Frozen] Mock<ITaxAuditor> accountingSystemMock,
PreferentialTaxRateCalculator sut)
{
sut.CalculateForOrder(orderNumber, orderSubtotal);
accountingSystemMock
.Verify(mock => mock.AuditTaxCalculation(orderNumber, orderSubtotal, expectedTaxRate, expectedTaxAmount));
}It would be helpful in the tests for this rate table to be always populated with predictable values that help cover particular scenarios.
So register a particular value to always use for IReadOnlyDictionary<string, decimal>
var fixture = new Fixture();
fixture.Customize(new AutoMoqCustomization());
+ fixture.Register<IReadOnlyDictionary<string, decimal>>(
+ () =>
+ new Dictionary<string, decimal>
+ {
+ { "123456", 0.05m }
+ });
return fixture;Instance Factory methods can take parameters that will be populated using the same rules as test method paremeters - if an already [Frozen] value is available for that type it will be used. Otherwise a new instance will be created and provided to the factory method
- fixture.Register<IReadOnlyDictionary<string, decimal>>(
- () =>
- new Dictionary<string, decimal>
- {
- { "123456", 0.05m }
- });
+ fixture.Register(
+ (decimal otherRate) =>
+ {
+ var result = new Dictionary<string, decimal>
+ {
+ { "123456", 0.05m },
+ { "abcdef", otherRate }
+ };
+ return (IReadOnlyDictionary<string, decimal>)result;
+ });When multiple classes implement an interface AutoFixture / AutoMoq needs some help to decide when creating a (not mocked) instance.
public class ConsoleTaxAuditor : ITaxAuditor
{
public void AuditTaxCalculation(string orderNumber, decimal orderSubtotal, decimal taxRate, decimal tax)
{
Console.WriteLine($"{orderNumber}: rate:{taxRate} tax:{tax}");
}
}
public class NoOpTaxAuditor : ITaxAuditor
{
public void AuditTaxCalculation(string orderNumber, decimal orderSubtotal, decimal taxRate, decimal tax) { }
} fixture.Register<IReadOnlyDictionary<string, decimal>>(
() =>
new Dictionary<string, decimal>
{
{ "123456", 0.05m }
});
+ fixture.Customizations.Add(new TypeRelay(typeof(ITaxAuditor), typeof(NoOpTaxAuditor)));
return fixture;For more complex instance creation factories a Specimen Builder is registered.
var fixture = new Fixture();
+ fixture.Customizations.Add(new DateOnlySpecimenBuilder());
+ fixture.Customizations.Add(new TimeOnlySpecimenBuilder());
fixture.Customize(new AutoMoqCustomization());note: See the solution for implementation details DateOnlySpecimenBuilder.cs and TimeOnlySpecimenBuilder.cs
Even more complex customisations can be provided.
e.g. NodaTime has a customisation that covers a range of its types and scenarios.
var fixture = new Fixture();
+ fixture.Customize(new NodaTimeCustomization());
fixture.Customizations.Add(new DateOnlySpecimenBuilder());
fixture.Customizations.Add(new TimeOnlySpecimenBuilder());I find mocking the sorts of things I use AutoMapper for to be counter productive - I'd rather just have the real mappers configured and available for tests and systems under test to use.
var fixture = new Fixture();
fixture.Customizations.Add(new DateOnlySpecimenBuilder());
fixture.Customizations.Add(new TimeOnlySpecimenBuilder());
+ fixture.Register(MapperFactory.CreateProfiles);
fixture.Customize(new AutoMoqCustomization());
where MapperFactory.CreateProfiles looks something like:
public static class MapperFactory
{
public static IMapper CreateProfiles()
{
var serviceProvider = new ServiceCollection()
.AddAutoMapper(
cfg =>
{
cfg.AddProfile<xxxProfile>();
cfg.AddProfile<yyyProfile>();
cfg.AddProfile<zzzProfile>();
})
.BuildServiceProvider();
var service = serviceProvider.GetRequiredService<IMapper>();
service.ConfigurationProvider.AssertConfigurationIsValid();
return service;
}
}RichardSzalay.MockHttp is a handy library for testing classes that use an HttpClient. There are several steps required to configure its mock handler and create the HttpClient that uses the mock handler.
var fixture = new Fixture();
fixture.Customize(new AutoMoqCustomization());
+ fixture.Register(
+ (MockHttpMessageHandler mockHttpHandler, Uri baseAddress) =>
+ {
+ var httpClient = mockHttpHandler.ToHttpClient();
+ httpClient.BaseAddress = baseAddress;
+ return httpClient;
+ });A class:
public class WebTaxAuditor : ITaxAuditor
{
private readonly HttpClient _httpClient;
public WebTaxAuditor(HttpClient httpClient) => _httpClient = httpClient;
public async Task AuditTaxCalculation(string orderNumber, decimal orderSubtotal, decimal taxRate, decimal tax)
{
var requestBody = new{ OrderNumber = orderNumber, TaxRate = taxRate, Tax = tax};
using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, "/");
httpRequestMessage.Content = JsonContent.Create(requestBody);
await _httpClient.SendAsync(httpRequestMessage);
}
}And a test:
[Theory]
[AutoMoqData]
public async Task WHEN_AuditTaxCalculation_THEN_post_to_server(
string orderNumber,
decimal orderSubtotal,
decimal taxRate,
decimal tax,
[Frozen] MockHttpMessageHandler mockHttpMessageHandler,
WebTaxAuditor sut)
{
mockHttpMessageHandler
.When(HttpMethod.Post, "/")
.Respond(HttpStatusCode.Created);
await sut.AuditTaxCalculation(orderNumber, orderSubtotal, taxRate, tax);
mockHttpMessageHandler.VerifyNoOutstandingExpectation();
}What's happening here?
Following the of parameters in the test:
- a
MockHttpMessageHandleris created byAutoFixture/AutoMoqto fulfil the test parameter[Frozen] MockHttpMessageHandler mockHttpMessageHandler,.MockHttpMessageHandlerhas a default constructor with no parameters - soAutoFixture/AutoMoqhas no problems with this. - When creating the
WebTaxAudtorsystem under test it determines it needs anHttpClientand looks through the registered factories and finds the one that that takes two parameters(MockHttpMessageHandler mockHttpHandler, Uri baseAddress) => ... - The test parameter list has
[Frozen]theMockHttpMessageHandler- so it uses that one already created. And it generates aUrito go into the other parameter. The factory method is called and anHttpClientis available for theWebTaxAuditorto use.
If the system under test uses IHttpClientFactory to create its own HttpClients then AutoFixture / AutoMoq can learn to provide those too:
fixture.Register(
(MockHttpMessageHandler mockHttpHandler, Uri baseAddress) =>
{
var httpClient = mockHttpHandler.ToHttpClient();
httpClient.BaseAddress = baseAddress;
return httpClient;
});
+ fixture.Register((HttpClient httpClient, Mock<IHttpClientFactory> httpClientFactoryMock) =>
+ {
+ httpClientFactoryMock
+ .Setup(x => x.CreateClient(It.IsAny<string>()))
+ .Returns(httpClient);
+
+ return httpClientFactoryMock.Object;
+ });note: RichardSzalay.MockHttp is available from https://github.com/richardszalay/mockhttp / https://www.nuget.org/packages/RichardSzalay.MockHttp/
MELT is a handy library for testing logging related calls.
var fixture = new Fixture();
fixture.Customize(new AutoMoqCustomization());
+ fixture.Register(TestLoggerFactory.Create);
+ fixture.Customizations.Add(new LoggerSpecimenBuilder());See LoggerSpecimenBuilder.cs for implementation.
public class LoggingTaxAuditor : ITaxAuditor
{
private readonly ILogger _logger;
public LoggingTaxAuditor(ILogger<LoggingTaxAuditor> logger) => _logger = logger;
public Task AuditTaxCalculation(string orderNumber, decimal orderSubtotal, decimal taxRate, decimal tax)
{
_logger.LogInformation("order number: {OrderNumber} has tax of {Tax} at rate: {TaxRate}", orderNumber, tax, taxRate);
return Task.CompletedTask;
}
}And a test:
[Theory]
[AutoMoqData]
public async Task WHEN_AuditTaxCalculation_THEN_log_as_information(
string orderNumber,
decimal orderSubtotal,
decimal taxRate,
decimal tax,
[Frozen] ITestLoggerFactory loggerFactory,
LoggingTaxAuditor sut)
{
await sut.AuditTaxCalculation(orderNumber, orderSubtotal, taxRate, tax);
loggerFactory.Sink.LogEntries.Where(x => x.LogLevel == LogLevel.Information).Should().HaveCount(1);
}I wrote the LoggerVerifier to help out (see LoggerVerifier.cs for implementation). So now the test can be a bit more succinct around the logging verification:
- loggerFactory.Sink.LogEntries.Where(x => x.LogLevel == LogLevel.Information).Should().HaveCount(1);
+ loggerFactory.VerifyMessageLogged(expectedLogLevel: LogLevel.Information, times: Times.Once());note: MELT is available from https://github.com/alefranz/MELT / https://www.nuget.org/packages/MELT
Given a class:
public class ExternalRateTaxCalculator
{
private readonly Func<string, decimal, decimal> _externalCalculator;
public ExternalRateTaxCalculator(Func<string, decimal, decimal> externalCalculator) => _externalCalculator = externalCalculator;
public decimal CalculateForOrder(string orderNumber, decimal orderSubtotal) => _externalCalculator(orderNumber, orderSubtotal);
}AutoFixture / AutoMoq needs to be told to generate delegates:
- fixture.Customize(new AutoMoqCustomization());
+ fixture.Customize(new AutoMoqCustomization { GenerateDelegates = true });A test could look like:
[Theory]
[AutoMoqData]
public void WHEN_calculateForOrder_THEN_delegate_to_external_calculator(
string orderNumber,
decimal orderSubtotal,
decimal expectedTaxAmount,
[Frozen] Mock<Func<string, decimal, decimal>> externalCalculatorMock,
ExternalRateTaxCalculator sut)
{
externalCalculatorMock
.Setup(mock => mock(orderNumber, orderSubtotal))
.Returns(expectedTaxAmount);
var result = sut.CalculateForOrder(orderNumber, orderSubtotal);
result.Should().Be(expectedTaxAmount);
}- complex object trees: can be expensive (take a long time) to auto generate. Note: Perfomance can be improved by registering custom
ISpecimenBuilders for key parts of the complex object tree - starting with the root. (I will write some examples of this in the near future) - looping/recusive object trees: can be handled but will need help. The specific solution will depend on the object tree and how it is expected to be populated - check the
AutoFixturedocumentation.