1

I am trying to Mock Redlock

I have the test below

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 is a simple mock class that implements 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); is a call to a seperate event class

I have shown this below. This has been simplified, but using the code below and the test above the null reference error can be reproduced

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

When I try to call this I expect my object to be returned, but instead I get null

On the line if (!redlock.IsAcquired) redLock is null

What is missing?

Paul
  • 2,773
  • 7
  • 41
  • 96

1 Answers1

3

The definition of 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);

requires a nullable CancellationToken

CancellationToken? cancellationToken = null

But the setup of the mock uses

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

Because the setup expects the non-nullable struct but when invoked the nullable CancellationToken? is what will be passed even though it is null,

The mock will return null by default because the setup does not match what was actually invoked.

Once the correct type was used the factory was able to return the desired mock

//...

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

//...
Nkosi
  • 235,767
  • 35
  • 427
  • 472