我在我的 .NET 6 项目中使用了 CQRS 模式。我有一个
CreateQueryCommandHandler
。它的访问修饰符设置为 internal
我无法更改。我正在尝试对我的命令处理程序进行单元测试。
我在我的应用程序项目中进行了更改,其中包含我的命令,以提供对我的单元测试项目的内部类访问权限。但仅仅添加这个并不能解决问题。
我也必须补充
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=3483847384783478347.....
如果没有将此行添加到我的创建命令处理程序类中,我会收到此错误:
System.ArgumentException:无法为 Microsoft.Extensions.Logging.ILogger`1[[QueryStore.Application.Queries.CreateQueryCommandHandler, QueryStore.Application, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]] 创建代理,因为类型 QueryStore.Application.Queries.CreateQueryCommandHandler 不可访问。将其设为公开或内部,并使用 [程序集:InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000....")] 属性标记程序集,因为程序集 Microsoft.Extensions.Logging.Abstractions 是强名称的。 (参数“interfaceToProxy”)
堆栈跟踪:
在 Castle.DynamicProxy.DefaultProxyBuilder.AssertValidTypeForTarget(类型类型,类型目标,字符串参数名称)
在 Castle.DynamicProxy.DefaultProxyBuilder.AssertValidTypeForTarget(类型类型,类型目标,字符串 paramName)
在 Castle.DynamicProxy.DefaultProxyBuilder.AssertValidType(类型目标,字符串 paramName)
在Castle.DynamicProxy.DefaultProxyBuilder.CreateInterfaceProxyTypeWithoutTarget(类型interfaceToProxy,类型[]additionalInterfacesToProxy,ProxyGenerationOptions选项) 在Castle.DynamicProxy.ProxyGenerator.CreateInterfaceProxyTypeWithoutTarget(类型interfaceToProxy,类型[]additionalInterfacesToProxy,ProxyGenerationOptions选项) 在Castle.DynamicProxy.ProxyGenerator.CreateInterfaceProxyWithoutTarget(类型interfaceToProxy,类型[]additionalInterfacesToProxy,ProxyGenerationOptions选项,IInterceptor[]拦截器) 在//src/Moq/Interception/CastleProxyFactory.cs中的Moq.CastleProxyFactory.CreateProxy(类型mockType,IInterceptor拦截器,Type []接口,Object []参数):第98行 在 Moq.Mock1.cs:第 502 行 在 Moq.Mock1.InitializeInstance() in /_/src/Moq/Mock
1.cs:第 516 行 在 //src/Moq/Mock.cs 中的 Moq.Mock.get_Object() 处:第 180 行 在 Moq.Mock1.OnGetObject() in /_/src/Moq/Mock
1.cs:第 453 行 在 /home/hardik/Desktop/queryhub/src/Application/tests/Queries/CreateQueryCommandHandlerTests.cs 中的 QueryStore.Application.UnitTests.Queries.Commands.CreateQueryCommandHandlerTests.Handle_Should_ReturnSuccessResult_WhenQueryIsAddedToDB() 处:第 127 行 在 /home/hardik/Desktop/queryhub/src/Application/tests/Queries/CreateQueryCommandHandlerTests.cs 中的 QueryStore.Application.UnitTests.Queries.Commands.CreateQueryCommandHandlerTests.Handle_Should_ReturnSuccessResult_WhenQueryIsAddedToDB() 处:第 133 行 在 System.Threading.Tasks.Task.<>c.b__128_0(对象状态)1.get_Object() in /_/src/Moq/Mock
失败的 QueryStore.Application.UnitTests.Queries.Commands.CreateQueryCommandHandlerTests.Handle_Should_ReturnErrorResult_WhenUnknownErrorOccurs [151 毫秒]
错误信息:
System.ArgumentException :无法为类型 Microsoft.Extensions.Logging.ILogger`1[[QueryStore.Application.Queries.CreateQueryCommandHandler, QueryStore.Application, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]] 创建代理,因为类型QueryStore.Application.Queries.CreateQueryCommandHandler 不可访问。将其设为公开或内部,并使用 [程序集:InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000....")] 属性标记程序集,因为程序集 Microsoft.Extensions.Logging.Abstractions 是强名称的。 (参数“interfaceToProxy”)
添加动态代理后,所有测试用例都可以正常工作。如果有人能向我解释为什么这是有效的,我将不胜感激。如果可能的话如何删除它。使我的代码更加简洁。我也希望有更好的测试策略。
我的
CommandHandler
代码:
using FluentValidation;
using MediatR;
using Microsoft.Extensions.Logging;
using QueryStore.Application.Common;
using System.Data.Common;
namespace QueryStore.Application.Queries;
internal sealed class CreateQueryCommandHandler : IRequestHandler<CreateQueryCommand, Result<string>>
{
private readonly IQueryStoreDbContext _context;
private readonly IValidator<CreateQueryCommand> _validator;
private readonly ILogger _logger;
public CreateQueryCommandHandler(
IQueryStoreDbContext context,
IValidator<CreateQueryCommand> validator,
ILogger<CreateQueryCommandHandler> logger)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
_validator = validator ?? throw new ArgumentNullException(nameof(validator));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task<Result<string>> Handle(CreateQueryCommand request, CancellationToken cancellationToken)
{
var validationResult = await _validator.ValidateAsync(request, cancellationToken);
if (!validationResult.IsValid)
{
return Result.Error(validationResult.Errors.Select(e => new Error($"{e.PropertyName}", e.ErrorMessage)));
}
try
{
var entity = request.AsQuery();
_context.Queries.Add(entity);
await _context.SaveChangesAsync(cancellationToken: cancellationToken);
return Result<string>.Success(entity.Id);
}
catch (DbException ex)
{
_logger.LogError(ex, "An error occurred while saving query to database");
return Result.Error(new Error("error", ex.Message));
}
catch (Exception ex)
{
_logger.LogError(ex, "An unknown error occurred while saving query");
return Result.Error(new Error("error", ex.Message));
}
}
}
单元测试代码:
using System.Data.Common;
using FluentAssertions;
using FluentValidation;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Moq;
using QueryStore.Application.Queries;
using QueryStore.Domain;
namespace QueryStore.Application.UnitTests.Queries.Commands;
public class CreateQueryCommandHandlerTests
{
private readonly Mock<IQueryStoreDbContext> _contextMock = new Mock<IQueryStoreDbContext>();
private readonly IValidator<CreateQueryCommand> _validator = new CreateQueryCommandValidator();
private readonly Mock<ILogger<CreateQueryCommandHandler>> _loggerMock =
new Mock<ILogger<CreateQueryCommandHandler>>();
[Fact]
public async void Handle_Should_ReturnSuccessResult_WhenQueryIsAddedToDB()
{
// Arrange
var command = new CreateQueryCommand()
{
Name = "Some Name",
Content = "Select * from AbcCompany",
Description = "Some description about the query.",
Tags = new[] { "company", "import" }
};
var options = new DbContextOptionsBuilder<QueryStoreDbContext>()
.UseInMemoryDatabase("TestDatabase")
.Options;
await using var dbContext = new QueryStoreDbContext(options);
// Act
var validationResult = await _validator.ValidateAsync(command);
var handler = new CreateQueryCommandHandler(dbContext, _validator, _loggerMock.Object);
var result = await handler.Handle(command, cancellationToken: CancellationToken.None);
// Assert
validationResult.IsValid.Should().BeTrue();
result.IsSuccess.Should().BeTrue();
}
[Fact]
public async Task Handle_Should_ReturnErrorResult_WhenUnknownErrorOccurs()
{
// Arrange
var command = new CreateQueryCommand()
{
Name = "Some Name",
Content = "Select * from AbcCompany",
Description = "Some description about the query.",
Tags = new[] { "company", "import" }
};
_contextMock.Setup(context =>
context.Queries.Add(It.IsAny<Query>())
).Throws(new Exception("Unknown error"));
// Act
var handler = new CreateQueryCommandHandler(_contextMock.Object, _validator, _loggerMock.Object);
var result = await handler.Handle(command, cancellationToken: CancellationToken.None);
// Assert
result.IsSuccess.Should().BeFalse();
result.Errors.Should().NotBeEmpty();
}
}
Moq
Moq
内部使用Castle.DynamicProxy
库来生成模拟Castle.DynamicProxy
在运行时发出模拟代码,并将该代码放置在名为 DynamicProxyGenAssembly2
CreateQueryCommandHandler
。根据你的第二个问题 - 我不知道更好的测试方法不需要该属性。