Проверка модульного тестирования с помощью MediatR PipelineBehavior - PullRequest
0 голосов
/ 15 марта 2020

Я использую FluentValidation и MediatR PipelineBehavior для проверки запросов CQRS. Как мне проверить это поведение в моих модульных тестах?

  1. Используйте тестовые расширения FluentValidation, и я тестирую только правила.

    [Theory]
    [InlineData(null)]
    [InlineData("")]
    [InlineData("   ")]
    public void Should_have_error_when_name_is_empty(string recipeName)
    {
        validator.ShouldHaveValidationErrorFor(recipe => recipe.Name, recipeName);
    }
    
  2. Проверка запроса вручную в модульном тесте

    [Theory]
    [InlineData("")]
    [InlineData("  ")]
    public async Task Should_not_create_recipe_when_name_is_empty(string recipeName)
    {
        var createRecipeCommand = new CreateRecipeCommand
        {
            Name = recipeName,
        };
    
        var validator = new CreateRecipeCommandValidator();
        var validationResult = validator.Validate(createRecipeCommand);
        validationResult.Errors.Should().BeEmpty();
    }
    
  3. Инициализация поведения трубопровода

    [Theory]
    [InlineData("")]
    [InlineData("  ")]
    public async Task Should_not_create_recipe_when_name_is_empty(string recipeName)
    {
        var createRecipeCommand = new CreateRecipeCommand
        {
            Name = recipeName
        };
    
        var createRecipeCommandHandler = new CreateRecipeCommand.Handler(_context);
    
        var validationBehavior = new ValidationBehavior<CreateRecipeCommand, MediatR.Unit>(new List<CreateRecipeCommandValidator>()
        {
            new CreateRecipeCommandValidator()
        });
    
        await Assert.ThrowsAsync<Application.Common.Exceptions.ValidationException>(() => 
            validationBehavior.Handle(createRecipeCommand, CancellationToken.None, () =>
            {
                return createRecipeCommandHandler.Handle(createRecipeCommand, CancellationToken.None);
            })
        );
    }
    

Или следует Я использую больше из них?

Класс ValidationBehavior:

public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IRequest<TResponse>
{
    private readonly IEnumerable<IValidator<TRequest>> _validators;

    public RequestValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
    {
        _validators = validators;
    }

    public Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
    {
        var context = new ValidationContext(request);

        var failures = _validators
            .Select(v => v.Validate(context))
            .SelectMany(result => result.Errors)
            .Where(f => f != null)
            .ToList();

        if (failures.Count != 0)
        {
            throw new ValidationException(failures);
        }

        return next();
    }
}

1 Ответ

0 голосов
/ 15 марта 2020

Я думаю, что все ваши примеры в порядке. Если они охватывают ваш код, то они предоставляют то, что вам нужно.

То, что я собираюсь описать, - это немного другой подход. Я приведу некоторые сведения.

Мы используем Mediatr, FluentValidation в Core (2.1). Мы завернули реализацию Mediatr, и вот что мы делаем:

У нас есть обобщенный c пре-обработчик (работает только для каждого обработчика) и ищет FluentValdator для команды / запрос приходит. Если он не может найти тот, который соответствует, он просто проходит. Если он это сделает, он запустит его, и если он потерпит неудачу, проверка получит результаты и вернет BadRequest с нашей стандартной проверкой в ​​ответе. У нас также есть возможность получить фабрику проверки в бизнес-обработчиках, чтобы они могли запускаться вручную. Это просто означает, что для разработчика требуется больше работы!

Итак, чтобы проверить это, мы используем Microsoft.AspNetCore.TestHost для создания конечной точки, в которую могут попасть наши тесты. Преимущество этого заключается в том, что тестируется весь конвейер Mediatr (включая проверку).

Итак, у нас есть такие вещи:

var builder = WebHost.CreateDefaultBuilder()
                .UseStartup<TStartup>()
                .UseEnvironment(EnvironmentName.Development)
                .ConfigureTestServices(
                    services =>
                    {
                        services.AddTransient((a) => this.SomeMockService.Object);
                    });

            this.Server = new TestServer(builder);
            this.Services = this.Server.Host.Services;
            this.Client = this.Server.CreateClient();
            this.Client.BaseAddress = new Uri("http://localhost");

Это определяет вещи, которые будет тестировать наш тестовый сервер (возможно, в нисходящем направлении). http class et c) и различные другие вещи.

Затем мы можем достичь нашей фактической конечной точки контроллера. Итак, мы проверяем, что мы зарегистрировали все и весь пиплайн.

Похоже на это (пример только для проверки немного проверки):

publi c SomeControllerTests (TestServerFixture testServerFixture): base (testServerFixture) {}

[Fact]
public async Task SomeController_Titles_Fails_With_Expected_Validation_Error()
{
    // Setup whatever you need to do to make it fail....

    var response = await this.GetAsync("/somedata/titles");

    response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
    var responseAsString = await response.Content.ReadAsStringAsync();
    var actualResponse = Newtonsoft.Json.JsonConvert.DeserializeObject<ValidationStuff);

    actualResponse.Should().NotBeNull();
    actualResponse.Should().HaveCount(1);
    actualResponse.[0].Message.Should().Be("A message");
}

Как я уже сказал, я думаю, что любой ваш выбор сделает то, что вам нужно. Если бы мне пришлось выбирать с включенной головкой тестового модуля (а это только личный выбор), я бы go с 2): -)

Мы обнаружили, что больше тестов системы / интеграции действительно работает. хорошо, когда твой обработчик очень прост. Когда они становятся более сложными (у нас есть один с приблизительно 12 обработчиками плюс около 6, которые вы просто получаете с помощью нашей оболочки), мы используем их вместе с индивидуальными тестами обработчиков, которые обычно соответствуют тому, что вы сделали с 2) или с 3).

Для получения дополнительной информации о системных / интеграционных тестах эта ссылка должна помочь. https://fullstackmark.com/post/20/painless-integration-testing-with-aspnet-core-web-api

Надеюсь, это поможет или, по крайней мере, даст вам пищу для размышлений: -)

...