#c# #unit-testing #mocking #redlock.net
Вопрос:
Я пытаюсь посмеяться над Редлоком
У меня есть тест ниже
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
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
указано значение null
Чего не хватает?
Ответ №1:
Определение понятия 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?
Потому что установка ожидает ненулевую структуру, но при вызове значение null CancellationToken?
будет передано, даже если оно равно нулю,
Макет по умолчанию вернет значение null, поскольку настройка не соответствует тому, что было фактически вызвано.
Как только был использован правильный тип, фабрика смогла вернуть желаемый макет
//...
redLockFactoryMock
.Setup(x => x.CreateLockAsync(It.IsAny<string>(),
It.IsAny<TimeSpan>(), It.IsAny<TimeSpan>(),
It.IsAny<TimeSpan>(), It.IsAny<CancellationToken?>()))
.ReturnsAsync(mock);
//...