通过 MediatR PipelineBehavior 进行单元测试验证 [英] Unit testing validation through MediatR PipelineBehavior

查看:37
本文介绍了通过 MediatR PipelineBehavior 进行单元测试验证的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用 FluentValidation 和 MediatR PipelineBehavior 来验证 CQRS 请求.我应该如何在我的单元测试中测试这种行为?

I'm using FluentValidation and MediatR PipelineBehavior to validate the CQRS requests. How should I test this behavior in my unit tests?

  1. 使用 FluentValidation 的测试扩展,我只进行测试规则.

[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData("   ")]
public void Should_have_error_when_name_is_empty(string recipeName)
{
    validator.ShouldHaveValidationErrorFor(recipe => recipe.Name, recipeName);
}

  • 在单元测试中手动验证请求

  • Manually validate the request in the unit test

    [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();
    }
    

  • 初始化流水线行为

  • Initialize the PipelineBehavior

    [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);
            })
        );
    }
    

  • 或者我应该使用更多这些吗?

    Or should I use more of these?

    ValidationBehavior 类:

    The ValidationBehavior class:

    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();
        }
    }
    

    推荐答案

    我认为你所有的例子都很好.如果他们涵盖了您的代码,那么他们正在提供您需要的东西.

    I think all your examples are fine. If they cover your code then they are providing what you need.

    我要描述的是一种略有不同的方法.我将提供一些背景信息.

    What I going to describe is a slightly different approach. I will provide some background.

    我们在 Core (2.1) 中使用 Mediatr、FluentValidation.我们已经包装了 Mediatr 实现,这是我们要做的:

    We use Mediatr, FluentValidation in Core (2.1). We have wrapped the Mediatr implementation and here is what we do:

    我们有一个通用的预处理程序(只为每个处理程序运行)并为传入的命令/查询寻找一个 FluentValdator.如果找不到匹配的,它就通过在.如果是,它会运行它,如果它失败,验证将获取结果并在响应中返回一个带有我们标准验证 guff 的 BadRequest.我们还能够在业务处理程序中获取验证工厂,以便手动运行它们.只是意味着开发人员需要做更多的工作!

    We have a generic pre-handler (just runs for every handler) and looks for a FluentValdator for the command/query coming in. If it can't find one that matches, it just passes on. If it does it will run it and if it fails validation will grab the results and return a BadRequest with our standard validation guff in the response. We also have the ability to grab the a validation factory in the business handlers so they van be run manually. Just means a bit more work for the developer!

    因此,为了对此进行测试,我们使用 Microsoft.AspNetCore.TestHost 来创建我们的测试可以访问的端点.这样做的好处是可以测试整个 Mediatr 管道(包括验证).

    So to test this we use Microsoft.AspNetCore.TestHost to create an endpoint our tests can hit. The benefit of this is that test the whole Mediatr pipeline (including validation).

    所以我们有这样的事情:

    So we have this sort of thing:

    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 类等)和其他各种内容.

    This defines things our test server will mock (possibly downstream http class etc) and various other stuff.

    然后我们可以点击我们实际的控制器端点.所以我们测试我们已经注册了所有东西和整个管道.

    We can then hit our actual controller endpoint. So we test we have registered everything and the whole pipleline.

    看起来像这样(一个例子只是为了测试一些验证):

    Looks like this (an example just to test a bit of validation):

    public SomeControllerTests(TestServerFixture testServerFixture):基础(测试服务器夹具){}

    public 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");
    }
    

    正如我所说,我认为您的任何选择都会满足您的需求.如果我不得不选择我的单元测试(这只是个人选择),我会选择 2) :-)

    As I say, I think any of your choices will do what you need. If I had to pick with my unit test head on (and this is just a personal choice), I would go with 2) :-)

    我们发现,当您的处理程序管道非常简单时,更多系统/集成测试路线非常有效.当它们变得更复杂时(我们有一个包含大约 12 个处理程序加上您使用我们的包装器获得的大约 6 个处理程序),我们将它们与单独的处理程序测试一起使用,这些测试通常与您在 2) 或 3) 中所做的匹配.

    We have found the more system/integration test route works really well when your handler pipleline is quite simple. When they become more complex (we have one with approx 12 handlers plus around 6 that you just get by using our wrapper) we use them along with individual handler tests that usually match what you have done with either 2) or in 3).

    有关系统/集成测试的更多信息,此链接应该会有所帮助.https://fullstackmark.com/post/20/painless-integration-testing-with-aspnet-core-web-api

    For more info about the system/integration tests this link should help. https://fullstackmark.com/post/20/painless-integration-testing-with-aspnet-core-web-api

    我希望这会有所帮助,或者至少可以让您深思:-)

    I hope this helps or at least gives you some food for thought :-)

    这篇关于通过 MediatR PipelineBehavior 进行单元测试验证的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆