1

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?

anon_dcs3spp
  • 2,342
  • 2
  • 28
  • 62
  • Does this answer your question? [Assigning out/ref parameters in Moq](https://stackoverflow.com/questions/1068095/assigning-out-ref-parameters-in-moq) – devNull Sep 04 '20 at 12:53
  • @devnull Hi thanks for responding, appreciated :) On initial inspection of the linked question...I tried....```parsedDetectionModelMock.Setup(x => x.accept(visitorMock.Object, ref It.Ref.IsAny, serializeOptions));``` but becase Utf8JsonReader is a ref struct it cannot be used with generics. Moq have detailed the issue [here](https://github.com/moq/moq4/issues/979) but do not understand how to work around it.... – anon_dcs3spp Sep 04 '20 at 13:12
  • 1
    It's theoretically possible but ridiculously involved for a mocking framework to attempt to handle this (it involves generating a lot of tricky code at runtime, far more than the simple proxies they can use now). A workaround will need to be on your end -- don't use a mocking framework for types containing such methods, write the mocks yourself, like people did before mocking frameworks where a thing. At the very least write a class that implements the methods taking `ref struct`s explicitly; you can mock the other methods in the regular way (hopefully). – Jeroen Mostert Sep 04 '20 at 13:37
  • Thanks for response, appreciated :) Ok, so for classes with methods that accept ref struct parameters I write my own mocks. In this particular scenario that would be for IVisitor, IVisitorElement and later, the custom converter. – anon_dcs3spp Sep 04 '20 at 13:50

0 Answers0