This is as much a note to self as a blog post. I needed to test a method that was dependent on a API with a callback action where T was scoped internal in the library that I was testing. Rather than making the type public I went ahead and tried to mock it anyway 😀
The code that we want to test is called from third party API like
await hubProxyFactory .Create(hubUrl, configureConnection, async p => { proxy = p; await SendQueuedSubscriptions(); p.On<Message>("onEvent", OnEvent); }, Reconnected, FaultedConnection, ConnectionComplete);
Message is a private type and OnEvent is of type Action<Message>. Its the OnEvent method we want to test. So we need to mock On<T> without knowing T and we need to save a reference to the OnEvent even though T is unknown. It looks like this
mock.Setup(x => x.On("onEvent", It.IsAny<Action<It.IsAnyType>>())).Callback( (string e, MulticastDelegate callback) => { onEvent = callback; });
So I mock any calls to On<It.IsAny>, It.IsAny is Moq magic and it means that T can be of any type, were the first argument equals “onEvent” and the second argument is a Action where T can be of any type. I also need to hookup a callback so I can save a reference to the OnEvent delegate. One could think second argument would be Action<It.IsAnyType>. Only thing that worked was to give it the close to the metal MulticastDelegate.
To create the private type that you send into the callback you need to use reflection
protected DotNetClientTest() { messageType = typeof(EventProxy).Assembly.GetType("SignalR.EventAggregatorProxy.Client.DotNetCore.EventAggregation.EventProxy+Message"); typeProp = messageType.GetProperty("Type"); genericProp = messageType.GetProperty("GenericArguments"); eventProp = messageType.GetProperty("Event"); idProp = messageType.GetProperty("Id"); } protected void PublishEvent<T>(T @event, object constraint = null) { var type = typeof(T); var message = Activator.CreateInstance(messageType); typeProp.SetValue(message, type.GetFullNameWihoutGenerics()); genericProp.SetValue(message, type.GetGenericArguments().Select(t => t.FullName).ToArray()); eventProp.SetValue(message, JsonDocument.Parse(JsonSerializer.SerializeToUtf8Bytes(@event)).RootElement); onEvent.Method.Invoke(onEvent.Target, new[] { message }); }I instance the private type with reflection, set it members and then invoke the delegate.