I am a newbie trying to learn testing with xUnit and Moq.
I am in the process of writing a test for a custom Json.Text.Json.Serialization.JsonConverter and have hit a stumbling block.
The abstract Read method of JsonConverter accepts a ref struct Utf8JsonReader. This is overriden in my custom converter class.
My custom converter class creates a parse model that has an accept method (see IVisitorElement below). This hands over deserialization responsibility to a visitor instance (see IVisitor below) for deserializing the json into the parse model.
Custom Converter Class
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Logging;
using WebApp.Data.Serializers.Converters.Visitors;
namespace WebApp.Data.Serializers.Converters
{
public sealed class MotionDetectionConverter : JsonConverter<MotionDetection>
{
private readonly ILogger _log;
private readonly IVisitorElement<MotionDetection> _parseModel;
private IVisitor _visitor;
public MotionDetectionConverter(
IVisitor visitor,
IVisitorElement<MotionDetection> parseModel,
ILogger<MotionDetectionConverter> logger)
{
_log = logger ?? throw new ArgumentNullException(nameof(logger));
_visitor = visitor ?? throw new ArgumentNullException(nameof(visitor));
_parseModel = parseModel ?? throw new ArgumentNullException(nameof(parseModel));
}
public override MotionDetection Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
_parseModel.accept(_visitor, ref reader, options);
_log.LogInformation("\n{0}\n", _parseModel);
return _parseModel.ToModel();
}
public override void Write(Utf8JsonWriter writer, MotionDetection motionDetection, JsonSerializerOptions options) =>
throw new NotImplementedException("Serialization of MotionDetection objects is not implemented");
}
}
VisitorElement Interface
public interface IVisitorElement<ModelType>
{
void accept(IVisitor visitor, ref Utf8JsonReader reader, JsonSerializerOptions options);
ModelType ToModel();
}
Visitor Interface
public interface IVisitor {
void DeserializeMotionDetection(ParsedMotionDetection target, ref Utf8JsonReader reader, JsonSerializerOptions options);
void DeserializeMotionInfo(ParsedMotionInfo target, ref Utf8JsonReader reader, JsonSerializerOptions options);
void DeserializeMotionLocation(ParsedLocation target, ref Utf8JsonReader reader, JsonSerializerOptions options)
}
The test so far...
[Fact(Skip="How to mock a method that accepts a ref struct parameter?")]
public void MotionDetectionConverter_Deserialize_UnitTest()
{
MotionDetection expected = CreateMotionDetection();
Utf8JsonReader reader = CreateUtf8JsonReader();
var visitorMock = new Mock<IVisitor>();
var parsedDetectionModelMock = new Mock<IVisitorElement<MotionDetection>>();
var parsedInfoModelMock = new Mock<IVisitorElement<MotionInfo>>();
var converter = new MotionDetectionConverter(visitorMock.Object, parsedDetectionModelMock.Object, new NullLogger<MotionDetectionConverter>());
var infoConverter = new MotionInfoConverter(visitorMock.Object, parsedInfoModelMock.Object, new NullLogger<MotionInfoConverter>());
JsonSerializerOptions serializeOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
serializeOptions.Converters.Add(converter);
serializeOptions.Converters.Add(infoConverter);
// The Read method of my custom converter class calls the accept
// method of a Visitor class, passing the ref struct Utf8JsonReader
// and JsonSerializationOptions.
// I want to use a mock to check that this call is made to the
// accept method of the Visitor class.......
// but this raise a compile error detailed below.
parsedDetectionModelMock.Setup(x => x.accept(visitorMock, ref reader, serializeOptions));
}
When I try to mock a call to the accept method of the Visitor class:
parsedDetectionModelMock.Setup(x => x.accept(visitorMock, ref reader, serializeOptions));
I receive the following error:
Cannot use ref local 'reader' inside an anonymous method, lambda expression or query method csharp CS8175
How is it possible, using Moq, to test a method that accepts a ref struct as a parameter?
Looking at a recent Moq issue this appears to be a known limitation.
This question answers how to test a method has been called with ref parameters. However, it does not suggest a solution for ref structs. Since Utf8JsonReader is a ref struct it cannot be used in generics such as:
/// fails with cannot use Utf8JsonReader as a type parameter
parsedDetectionModelMock.Setup(x => x.accept(visitorMock.Object, ref It.Ref<Utf8JsonReader>.IsAny, serializeOptions));
How are developers testing similar scenarios?
Are there any alternative mocking frameworks capable of managing this?