Search Results for

    Show / Hide Table of Contents

    Intent.Application.MediatR.CRUD.Tests

    This module generates unit tests for using XUnit, NSubstitute, FluentAssertions and AutoFixture.

    Some of the terms used in this module:

    • SUT - System Under Test.

    Where are the tests generated?

    Once this module is installed and the Software Factory executed, you will notice a new solution folder and project like this in your IDE:

    Test project location

    What kind of tests are generated?

    This module only targets the MediatR CRUD services like this:

    Target MediatR CRUD services

    Only the mapped Commands and Queries that can be identified as CRUD-based services are selected for generating tests to test their respective Command/Query handlers.

    There are the following kinds of tests available:

    • Create Handler Tests
    • Update Handler Tests
    • Delete Handler Tests
    • Get-By-Id Handler Tests
    • Get-All Handler Tests
    • Paginated-Get Handler Tests
    • Fluent Validation Tests

    All of the above (except the paginated and fluent validation tests) will have tests also for nested entities.

    Nested Entities can be explained like this. If I have a domain like this:

    Domain with nested entity

    I can generate CRUD services like this:

    Nested CRUD services

    Those services will also have unit tests generated.

    Examples of Unit Tests

    CreateInvoiceCommandHandlerTests (Command Handler Test)

    This does a positive-test against the Command Handler.

    public class CreateInvoiceCommandHandlerTests
    {
        public static IEnumerable<object[]> GetSuccessfulResultTestData()
        {
            var fixture = new Fixture();
            yield return new object[] { fixture.Create<CreateInvoiceCommand>() };
        }
    
        [Theory]
        [MemberData(nameof(GetSuccessfulResultTestData))]
        public async Task Handle_WithValidCommand_AddsInvoiceToRepository(CreateInvoiceCommand testCommand)
        {
            // Arrange
            var invoiceRepository = Substitute.For<IInvoiceRepository>();
            var expectedInvoiceId = new Fixture().Create<System.Guid>();
            Invoice addedInvoice = null;
            invoiceRepository.OnAdd(ent => addedInvoice = ent);
            invoiceRepository.UnitOfWork
                .When(async x => await x.SaveChangesAsync(CancellationToken.None))
                .Do(_ => addedInvoice.Id = expectedInvoiceId);
    
            var sut = new CreateInvoiceCommandHandler(invoiceRepository);
    
            // Act
            var result = await sut.Handle(testCommand, CancellationToken.None);
    
            // Assert
            result.Should().Be(expectedInvoiceId);
            await invoiceRepository.UnitOfWork.Received(1).SaveChangesAsync();
            InvoiceAssertions.AssertEquivalent(testCommand, addedInvoice);
        }
    }
    
    Note

    GetSuccessfulResultTestData can be Intent-Ignored in order for user-defined tests data to be supplied.

    CreateInvoiceCommandValidatorTests (Fluent Validation Test)

    This does both positive and negative test cases against the Fluent Validator for the Command Handler.

    public class CreateInvoiceCommandValidatorTests
    {
        public static IEnumerable<object[]> GetSuccessfulResultTestData()
        {
            var fixture = new Fixture();
            var testCommand = fixture.Create<CreateInvoiceCommand>();
            yield return new object[] { testCommand };
        }
    
        [Theory]
        [MemberData(nameof(GetSuccessfulResultTestData))]
        public async Task Validate_WithValidCommand_PassesValidation(CreateInvoiceCommand testCommand)
        {
            // Arrange
            var validator = GetValidationBehaviour();
            var expectedId = new Fixture().Create<System.Guid>();
            // Act
            var result = await validator.Handle(testCommand, (c) => Task.FromResult(expectedId), CancellationToken.None);
    
            // Assert
            result.Should().Be(expectedId);
        }
    
        public static IEnumerable<object[]> GetFailedResultTestData()
        {
            var fixture = new Fixture();
            fixture.Customize<CreateInvoiceCommand>(comp => comp.With(x => x.InvoiceNo, () => default));
            var testCommand = fixture.Create<CreateInvoiceCommand>();
            yield return new object[] { testCommand, "InvoiceNo", "not be empty" };
        }
    
        [Theory]
        [MemberData(nameof(GetFailedResultTestData))]
        public async Task Validate_WithInvalidCommand_FailsValidation(
            CreateInvoiceCommand testCommand,
            string expectedPropertyName,
            string expectedPhrase)
        {
            // Arrange
            var validator = GetValidationBehaviour();
            var expectedId = new Fixture().Create<System.Guid>();
            // Act
            var act = async () => await validator.Handle(testCommand, (c) => Task.FromResult(expectedId), CancellationToken.None);
    
            // Assert
            act.Should().ThrowAsync<ValidationException>().Result
            .Which.Errors.Should().Contain(x => x.PropertyName == expectedPropertyName && x.ErrorMessage.Contains(expectedPhrase));
        }
    
        private ValidationBehaviour<CreateInvoiceCommand, System.Guid> GetValidationBehaviour()
        {
            return new ValidationBehaviour<CreateInvoiceCommand, System.Guid>(new[] { new CreateInvoiceCommandValidator() });
        }
    }
    
    Note

    GetSuccessfulResultTestData, GetFailedResultTestData can be Intent-Ignored in order for user-defined tests data to be supplied. GetValidationBehaviour can also be Intent-Ignored in order to control how a Validator is constructed especially when user-defined services are being injected in the constructor.

    • Edit this page
    ☀
    ☾
    In this article
    Back to top Copyright © 2017-, Intent Software Pte Ltd.