模拟 Redlock.CreateAsync 不返回模拟对象

问题描述

我正在尝试模拟红锁

我有下面的测试

using Moq;
using RedLockNet;
using System;
using System.Threading;
using System.Threading.Tasks;
using Xunit;

namespace RedLock.Tests 
{
   public class RedLockTests 
   {
       [Fact]
       public async Task TestMockingOfRedlock()
       {
            var redLockFactoryMock = new Mock<IdistributedLockFactory>();

            var mock = new MockRedlock();
            redLockFactoryMock.Setup(x => x.CreateLockAsync(It.IsAny<string>(),It.IsAny<TimeSpan>(),It.IsAny<CancellationToken>()))
            .ReturnsAsync(mock);

             var sut = new TestRedlockHandler(redLockFactoryMock.Object);

             var data = new MyEventData();
             await sut.Handle(data);
        }
    }
}

MockRedlock一个简单的模拟类,它实现了 IRedLock

public class MockRedlock: IRedLock
{
    public void dispose()
    {
        
    }

    public string Resource { get; }
    public string LockId { get; }
    public bool IsAcquired => true;
    public RedLockStatus Status => RedLockStatus.Acquired;
    public RedLockInstanceSummary InstanceSummary => new RedLockInstanceSummary();
    public int ExtendCount { get; }
}

await sut.Handle(data); 是对单独事件类的调用

我已经在下面展示了这一点。这个已经简化了,但是使用下面的代码和上面的测试可以重现空引用错误

public class MyEventData
{
    public string Id { get; set; }

    public MyEventData()
    {
        Id = Guid.NewGuid().ToString();
    }
}


public class TestRedlockHandler
{
    private IdistributedLockFactory _redLockFactory;

    public TestRedlockHandler(IdistributedLockFactory redLockFactory)
    {
        _redLockFactory = redLockFactory;
    }

    public async Task Handle(MyEventData data)
    {
        var lockexpiry = TimeSpan.FromMinutes(2.5);
        var waitspan = TimeSpan.FromMinutes(2);
        var retryspan = TimeSpan.FromSeconds(20);
        using (var redlock =
            await _redLockFactory.CreateLockAsync(data.Id.ToString(),lockexpiry,waitspan,retryspan,null))
        {
            if (!redlock.IsAcquired)
            {
                string errorMessage =
                    $"Did not acquire Lock on Lead {data.Id.ToString()}.  Aborting.\n " +
                    $"Acquired{redlock.InstanceSummary.Acquired} \n " +
                    $"Error{redlock.InstanceSummary.Error} \n" +
                    $"Conflicted {redlock.InstanceSummary.Conflicted} \n" +
                    $"Status {redlock.Status}";
                throw new Exception(errorMessage);
            }
        }
    }
}

当我尝试调用它时,我希望返回我的对象​​,但我得到的是 null

在行上 if (!redlock.IsAcquired) redLock 为空

缺少什么?

解决方法

CreateLockAsync 的定义

/// <summary>
/// Gets a RedLock using the factory's set of redis endpoints. You should check the IsAcquired property before performing actions.
/// Blocks and retries up to the specified time limits.
/// </summary>
/// <param name="resource">The resource string to lock on. Only one RedLock should be acquired for any given resource at once.</param>
/// <param name="expiryTime">How long the lock should be held for.
/// RedLocks will automatically extend if the process that created the RedLock is still alive and the RedLock hasn't been disposed.</param>
/// <param name="waitTime">How long to block for until a lock can be acquired.</param>
/// <param name="retryTime">How long to wait between retries when trying to acquire a lock.</param>
/// <param name="cancellationToken">CancellationToken to abort waiting for blocking lock.</param>
/// <returns>A RedLock object.</returns>
Task<IRedLock> CreateLockAsync(string resource,TimeSpan expiryTime,TimeSpan waitTime,TimeSpan retryTime,CancellationToken? cancellationToken = null);

需要一个可为空的 CancellationToken

CancellationToken? cancellationToken = null

但是模拟的设置使用

It.IsAny<CancellationToken>() //NOTE CancellationToken instead of CancellationToken?

因为设置需要不可为空的结构,但是当调用时,可以为空的 CancellationToken? 即使它为空也会被传递,

默认情况下,mock 将返回 null,因为设置与实际调用的内容不匹配。

一旦使用了正确的类型,工厂就能够返回所需的模拟

//...

redLockFactoryMock
    .Setup(x => x.CreateLockAsync(It.IsAny<string>(),It.IsAny<TimeSpan>(),It.IsAny<CancellationToken?>()))
    .ReturnsAsync(mock);

//...