模拟 Redlock.CreateAsync 不会 return 模拟对象

Mocking Redlock.CreateAsync does not return mocked object

我正在尝试模拟 Redlock

我有下面的测试

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<TimeSpan>(),
                It.IsAny<TimeSpan>(), It.IsAny<CancellationToken>()))
            .ReturnsAsync(mock);

             var sut = new TestRedlockHandler(redLockFactoryMock.Object);

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

MockRedlock 是一个实现 IRedLock

的简单模拟 class
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); 是对单独事件的调用 class

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

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

需要一个可为 null 的 CancellationToken

CancellationToken? cancellationToken = null

但是模拟的设置使用

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

因为设置需要不可为 null 的结构,但在调用可空结构时 CancellationToken? 将被传递,即使它为 null,

模拟将 return 默认为空,因为设置与实际调用的不匹配。

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

//...

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

//...