I'm learning to use AutoFixture and try to improve my unit tests. I want to modify one of my tests to use AutoData attribute and get mocks from method parameters.

Here is my unit test (it is currently passing):

[Theory, AutoData]
public async Task UpdateMetadata_PassesAllMessagesToSummaryGenerator(ConversationHistory history)
{
    // Arrange
    var conversationStorage = _fixture.Freeze<Mock<IConversationStorage>>();
    var summaryGeneratorMock = _fixture.Freeze<Mock<ISummariesMetadataGenerator>>();
    conversationStorage.Setup(s => s.GetHistory(It.IsAny<Guid>())).Returns(history);

    // Act
    _sut = _fixture.Create<ConversationMetadataGenerator>();
    await _sut.UpdateMetadata(Guid.NewGuid());

    // Assert
    Assert.That(history.Messages.Count, Is.GreaterThan(0));
    foreach (var message in history.Messages)
    {
        summaryGeneratorMock.Verify(x => x.GenerateSummariesFor(
            It.Is<string>(s => s.Contains(message.Text))
        ));
    }
}

Here is the same unit test but after modifications:

[Theory, AutoData]
public async Task UpdateMetadata_PassesAllMessagesToSummaryGenerator2(
    [Frozen] Mock<IConversationStorage> conversationStorage,
    [Frozen] Mock<ISummariesMetadataGenerator> summaryGeneratorMock,
    ConversationMetadataGenerator sut,
    ConversationHistory history)
{
    // Arrange
    conversationStorage.Setup(s => s.GetHistory(It.IsAny<Guid>())).Returns(history);

    // Act
    await sut.UpdateMetadata(Guid.NewGuid());

    // Assert
    Assert.That(history.Messages.Count, Is.GreaterThan(0));
    foreach (var message in history.Messages)
    {
        summaryGeneratorMock.Verify(x => x.GenerateSummariesFor(
            It.Is<string>(s => s.Contains(message.Text))
        ));
    }
}

The result of unit test from the second example is:

AutoFixture.ObjectCreationExceptionWithPath : AutoFixture was unable to create an instance from LlmConversation.Core.Storage.IConversationStorage because it's an interface. There's no single, most appropriate way to create an object implementing the interface, but you can help AutoFixture figure it out.

There is no problem to create an instance of IConversationStorage in the first unit test and at the same time it is not possible to create it in second test. Why is that?

1

There are 1 best solutions below

0
jarmanso7 On

Try using auto-mocking as described in AutoData Theories with AutoFixture, an article in AutoFixture's creator's blog:

AutoDataAttribute derives from xUnit.net's DataAttribute (just like InlineDataAttribute), and while we can use it as is, it becomes really powerful if we combine it with auto-mocking like this:

public class AutoMoqDataAttribute : AutoDataAttribute
{
    public AutoMoqDataAttribute()
        : base(new Fixture()
            .Customize(new AutoMoqCustomization()))
    {
    }
}

and then use AutoMoqData instead of AutoData in your test like this:

[Theory, AutoMoqData]
public async Task UpdateMetadata_PassesAllMessagesToSummaryGenerator2(
    [Frozen] Mock<IConversationStorage> conversationStorage,
    [Frozen] Mock<ISummariesMetadataGenerator> summaryGeneratorMock,
    ConversationMetadataGenerator sut,
    ConversationHistory history)
{
    // [...]
}