I'm trying to Moq a synchronization process, but I'm having issues with one specific part.
In my method I'm trying to Moq I perform the following:
public class SyncManager
{
private IPubHttpClient _pubHttpClient;
private ILogService _logService;
private Ilogger _logger;
public SyncManager(IPubHttpClient pubClient, ILogService logService ILogger<SyncManager> logger)
{
_pubHttpClient = pubClient;
_logService = logService;
_logger = logger;
}
public async Task Sync()
{
var syncStatus = SyncStatus.Error;
// get logs
var logs = await _logService.GetLogs();
foreach (var log in logs)
{
if (!string.IsNullOrEmpty(log.CostCode))
syncStatus = await GetAndSendCost(log);
elseif
syncStatus = await GetAndSendSort(log);
}
}
private async Task<SyncStatus> GetAndSendCost(Log log)
{
var cost = new Cost
{
CostCode = log.CostCode,
CostName = log.Description,
Active = log.Active
};
await _pubHttpClient.Push(new EventModel { Cost = cost, MessageType = log.Type.GetDescription() });
return SyncStatus.Success;
}
private async Task<SyncStatus> GetAndSendSort(Log log)
{
var sort = new Sort
{
SortCode = log.SortCode,
SortName = log.Description,
Active = log.Active
};
await _pubHttpClient.Push(new EventModel { Sort = sort, MessageType = log.Type.GetDescription() });
return SyncStatus.Success;
}
}
public class Log
{
public long Id { get; set; }
public string SortCode { get; set; }
public string CostCode { get; set; }
public string Description { get; set; }
public string Active { get; set; }
public AuditType Type { get; set; }
}
public class EventModel
{
public Cost Cost { get; set; }
public Sort Sort { get; set; }
public string MessageType { get; set; }
}
public enum AuditType
{
[Description("CREATE")]
Create = 0,
[Description("UPDATE")]
Update = 1,
[Description("DELETE")]
Delete = 2
}
public static class EnumExtensions
{
public static string GetDescription(this Enum enumValue)
{
return enumValue.GetType()
.GetMember(enumValue.ToString())
.First()
.GetCustomAttribute<DescriptionAttribute>()?
.Description ?? string.Empty;
}
}
My tests I have set up to like this:
public class SyncManagerTests
{
public readonly Mock<IPubHttpClient> _pubClientMock = new();
public readonly Mock<ILogService> _logServiceMock = new();
[Fact]
public async Task Should_Sync()
{
var mockedCost = new Cost { Active = Status.Active, CostCode = "0000", CostName = "UNIT TEST" };
var mockedSort = new Sort { Active = Status.Active, SortCode = "0001", SortName = "UNIT TEST" };
var mockedLogs = new List<Log> {
new Log { CostCode = mockedCost.CostCode, Description = mockedCost.CostName, Active = mockedCost.Active, Id = 1 },
new Log { SortCode = mockedSort.SortCode, Description = mockedSort.CostName, Active = mockedSort.Active, Id = 2 },
};
_logServiceMock.Setup(s => s.GetLogs()).ReturnsAsync(mockedLogs).Verifiable();
_pubClientMock.Setup(p => p.Push(It.Is<EventModel>(x => x.Cost == mockedCost && x.MessageType == "CREATE"))).Returns(Task.CompletedTask).Verifiable();
var syncManager = new SyncManager(_pubClientMock.Object, _logServiceMock.Object, Mock.Of<ILogger<SyncManager>>());
await syncManager.Sync();
_pubClientMock.Verify(p => p.Push(It.Is<EventModel>(
x => x.Cost.CostName == mockedCost.CostName
&& x.Cost.CostCode == mockedCost.CostCode
&& x.Cost.Active == mockedCost.Active
&& x.MessageType == "CREATE")));
}
}
When I run this test, every piece of code is called correctly and while debugging I see that the EventModel object
is being created with the correct values.
However in my test when I call _pubClientMock.Verify();
I get a System.NullReferenceException
:
It seems like the x.Cost
is NULL here.
Any idea why this property would be NULL or what I'm doing wrong here?
So to iterate again, actually calling the .Sync()
and stepping through the code with the debugger works perfectly. It's the _pubClientMock.Verify
that fails with with a NullReferenceException
.
CodePudding user response:
Can you provide more detail about Log you pass through GetAndSendCost() maybe the problem comes from that part, you can use It.IsAny<EventModel>()
// Arrange
Mock<IPubHttpClient> _pubClientMock = new Mock<IPubHttpClient>();
_pubClientMock.Setup(p => p.Push(It.IsAny<EventModel>())).Returns(Task.CompletedTask).Verifiable();
var syncManager = new SyncManager(_pubClientMock.Object, Mock.Of<ILogger<SyncManager>>());
// Act
await syncManager.Sync();
// Assert
_pubClientMock.Verify(p => p.Push(It.Is<EventModel>(
x => x.Cost.CostName == mockedCost.CostName
&& x.Cost.CostCode == mockedCost.CostCode
&& x.Cost.Active == mockedCost.Active
&& x.MessageType == "CREATE")));
CodePudding user response:
Similar to the one of the already provided answers I would suggest relaxing the setup to avoid issues with referential equality by using It.IsAny
. This will allow the test case to flow to completion.
As for the null reference error in the Verification, because the updated example will have a situation where one of the event models will have a null Cost
property value, you will need to check for null when verifying the predicate
[TestClass]
public class SyncManagerTests {
public readonly Mock<IPubHttpClient> _pubClientMock = new Mock<IPubHttpClient>();
public readonly Mock<ILogService> _logServiceMock = new Mock<ILogService>();
[Fact]
public async Task Should_Sync() {
var mockedCost = new Cost { Active = Status.Active, CostCode = "0000", CostName = "UNIT TEST" };
var mockedSort = new Sort { Active = Status.Active, SortCode = "0001", SortName = "UNIT TEST" };
var mockedLogs = new List<Log> {
new Log { CostCode = mockedCost.CostCode, Description = mockedCost.CostName, Active = mockedCost.Active, Id = 1 },
new Log { SortCode = mockedSort.SortCode, Description = mockedSort.SortName, Active = mockedSort.Active, Id = 2 },
};
_logServiceMock.Setup(s => s.GetLogs()).ReturnsAsync(mockedLogs);
_pubClientMock
.Setup(p => p.Push(It.IsAny<EventModel>())) //<-- NOTE THIS
.Returns(Task.CompletedTask);
var syncManager = new SyncManager(_pubClientMock.Object, _logServiceMock.Object, Mock.Of<ILogger<SyncManager>>());
await syncManager.Sync();
_pubClientMock.Verify(p => p.Push(It.Is<EventModel>(
x => x.Cost != null //<-- NOTE THE NULL CHECK
&& x.Cost.CostName == mockedCost.CostName
&& x.Cost.CostCode == mockedCost.CostCode
&& x.Cost.Active == mockedCost.Active
&& x.MessageType == "CREATE")));
}
}