LINQ 查询修改委托的模拟对象

问题描述 投票:0回答:1

有这样一种方法可以从数据库中获取一些数据:

public async Task<T> GetByFilter(Func<IQueryable<T>, IQueryable<T>>? queryModifier, CancellationToken cancellationToken = default)
{
    try
    {
        using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(GET_BY_FILTER_AWAITING));
        cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, cts.Token).Token;

        IQueryable<T> query = _dbSet;

        if (queryModifier is not null)
            query = queryModifier(query);

        return await query.FirstOrDefaultAsync(cancellationToken);
    }
    catch (OperationCanceledException)
    {
        throw new OperationCanceledException(REQUEST_TIMED_OUT);
    }
    catch (Exception ex)
    {
        _logger.LogCritical(ex.ToString(), nameof(_context), nameof(_dbSet));
        throw new OperationCanceledException(Message.ERROR);
    }
}

它将现有 LINQ 查询的修改委托作为参数:

Func<IQueryable<T>, IQueryable<T>>? queryModifier

接下来,在需要测试的方法中,这个函数用于接收用户的电子邮件:

var user = await userRepository.GetByFilter(query => query.Where(u => u.email.Equals(email.ToLowerInvariant())));

下面是需要测试的方法的完整代码:

public async Task<IActionResult> RecoveryAccount([FromQuery] string email)
{
    try
    {
        var user = await userRepository.GetByFilter(query => query.Where(u => u.email.Equals(email.ToLowerInvariant())));
        if (user is null)
            return StatusCode(404, new { message = Message.NOT_FOUND });

        string token = Guid.NewGuid().ToString("N") + Guid.NewGuid().ToString() + generate.GenerateKey();
        await recoveryHelper.CreateTokenTransaction(user, token);
        await emailSender.SendMessage(new EmailDto
        {
            username = user.username,
            email = email,
            subject = EmailMessage.RecoveryAccountHeader,
            message = EmailMessage.RecoveryAccountBody + $"{fileManager.GetReactAppUrl()}/auth/recovery?token={token}"
        });

        await redisCache.DeteteCacheByKeyPattern($"{ImmutableData.NOTIFICATIONS_PREFIX}{user.id}");

        return StatusCode(201, new { message = Message.EMAIL_SENT });
    }
    // Handling some exceptions, doesn't make sense to specify in the question
    catch (Exception ex)
    {
        return StatusCode(500, new { message = ex.Message });
    }
}

测试时,我需要使用特定的 Func

来模拟对此方法的调用

我这样做:

// Creating Mock Object UserRepository:
var userRepositoryMock = new Mock<IRepository<UserModel»();

// Creating specific Func
Func<IQueryable<UserModel>, IQueryable<UserModel» query = query => query.Where(u => u.email.Equals(email.ToLowerInvariant()));

// Mock my method with my Func
userRepositoryMock.Setup(x => x.GetByFilter(query, CancellationToken.None))
    .ReturnsAsync(user);

但是作为模拟的结果,我没有得到预期的user对象.ReturnsAsync(user),但是null

我认为这是因为比较了对委托的引用,而不是委托的值,所以不存在巧合,我不明白如何解决这个问题。

我注释掉了那些可能与问题没有太大关系的地方。

接下来是测试本身,其中需要模拟具有特定 Func 的方法:

[Fact]
public async Task RecoveryAccount_Success()
{
    var id = 1;
    var email = "[email protected]";
    var username = "air2921";
    Func<IQueryable<UserModel>, IQueryable<UserModel>> query = query => query.Where(u => u.email.Equals(email.ToLowerInvariant()));
    var user = new UserModel
    {
        id = id,
        username = username,
        email = email
    };

    var userRepositoryMock = new Mock<IRepository<UserModel>>();
    //var generateMock = new Mock<IGenerate>();
    //var emailSenderMock = new Mock<IEmailSender>();
    //var redisCacheMock = new Mock<IRedisCache>();
    //var fileManagerMock = new Mock<IFileManager>();
    //var recoveryServiceMock = new Mock<IRecoveryHelpers>();

    userRepositoryMock.Setup(x => x.GetByFilter(query, CancellationToken.None))
        .ReturnsAsync(user);
    //generateMock.Setup(x => x.GenerateKey()).Returns("8ifrnDa8a9nabJDfjTrfXsgfVIhCYGrZbN5HdtX0dK8=");
    //fileManagerMock.Setup(x => x.GetReactAppUrl()).Returns(string.Empty);

    var recoveryController = new RecoveryController(/*recoveryServiceMock.Object, null,*/ userRepositoryMock.Object/*,*/
        /*null, emailSenderMock.Object, redisCacheMock.Object, fileManagerMock.Object, generateMock.Object*/);

    var result = await recoveryController.RecoveryAccount(email);

    Assert.IsType<ObjectResult>(result);
    var objectResult = (ObjectResult)result;
    Assert.Equal(201, objectResult.StatusCode);

    //recoveryServiceMock.Verify(x => x.CreateTokenTransaction(user, It.Is<string>(q => q.Length >= 112)), Times.Once);
    //emailSenderMock.Verify(x => x.SendMessage(It.Is<EmailDto>(e => e.username == username && e.email == email)), Times.Once);
    //redisCacheMock.Verify(x => x.DeteteCacheByKeyPattern($"{ImmutableData.NOTIFICATIONS_PREFIX}{id}"), Times.Once);
}
c# unit-testing xunit
1个回答
0
投票

我的建议是创建一个自定义类来模拟您的存储库。类似于

MemoryRepository
,使用列表或字典作为后备存储,但在其他方面满足
IRepository
的接口。

编写单元测试时,您应该测试方法的“接口”。如果您的测试只是方法应调用的事物列表,那么您的测试可能会很脆弱,并且实现中的任何更改都可能会破坏您的测试。提供更完整的依赖项实现可能有助于避免这种脆弱性。

并不是说模拟框架有什么不好。但它们可能不是各种嘲笑的最佳工具。

请注意,正确重现数据库的行为可能很困难。并非所有编译的查询都可以转换为 SQL,并且某些行为将取决于所使用的数据库引擎。 EF 内存数据库 可能对某些类型的测试有用。使用数据库的实际实例对于其他测试很有用,在这些测试中,正确性比运行时之类的东西更重要。

© www.soinside.com 2019 - 2024. All rights reserved.