简体   繁体   中英

C# Moq - Mocking Action<T> parameter

I've been strugling with a moq setup for a while, and I'd like get some help from you, any help is appreciated.

Basic codes

public interface IMessageHub
{
    void Subscribe<T>(string topic, Action<T> callback);
}

Having message hub, where you can subscribe to topics, and if there is a message that was published, it calls the callback with the message.

Every message is derived from

public class MessageBase { }

And for the example, I have created 2 message types:

public class RefreshMessage : MessageBase { }

public class CancelMessage : MessageBase { }

In some part of the code, there is 2 subscribtion for the messages

...
MessageHub.Subscribe<RefreshMessage>("Refresh", RefreshMethod);
MessageHub.Subscribe<CancelMessage>("Cancel", CancelMethod);
...

The callback methods are private, so I can only get them through setup in the unit tests.

private void RefreshMethod(RefreshMessage message) { ... }
private void CancelMethod(CancelMessage message) { ... }

These parts are provided by a framework, so I can't change them.

Unit tests

In the unit tests I want to mock the MessageHub with a Moq , and mock the Subscribe method for every action, and get the callback method that the subscribe method was called with.

Normally, I would have to create 2 setups for every messagetype like this:

var messageHubMock = new Mock<IMessageHub>();
var methods = new List<dynamic>();

//setup for RefreshMessage
messageHubMock.Setup(_=>_.Subscribe(It.IsAny<string>,It.IsAny<Action<RefreshMessage>>())
.Callback<string,Action<RefreshMessage>>((s,a)=> methods.Add(a));

//setup for CancelMessage
messageHubMock.Setup(_=>_.Subscribe(It.IsAny<string>,It.IsAny<Action<CancelMessage>>())
.Callback<string,Action<CancelMessage>>((s,a)=> methods.Add(a));

Setup question

Question is, how can I do the 2 setup in 1 setup?

The following setups ARE NOT WORKING, but showing that those I've tried.

messageHubMock.Setup(_=>_.Subscribe(It.IsAny<string>,It.IsAny<Action<object>>())
.Callback<string,Action<object>>((s,a)=> methods.Add(a));

messageHubMock.Setup(_=>_.Subscribe(It.IsAny<string>,It.IsAny<Action<dynamic>>())
.Callback<string,Action<dynamic>>((s,a)=> methods.Add(a));

messageHubMock.Setup(_=>_.Subscribe(It.IsAny<string>,It.IsAny<object>())
.Callback<string,object>((s,a)=> methods.Add(a));
//does not compile
messageHubMock.Setup(_=>_.Subscribe(It.IsAny<string>,It.IsAny<Action>())
.Callback<string,Action>((s,a)=> methods.Add(a));

messageHubMock.Setup(_=>_.Subscribe(It.IsAny<string>,It.IsAny<Action<MessageBase>>())
.Callback<string,Action<MessageBase>>((s,a)=> methods.Add(a));

Every of these does not fit for the expression, so even though the subscibre was called in the code, Moq does not run the Callback on the setup, because the subscribe was called with "different" arguments.

I suppose I need to create my own expression filter logic on this. Can you help me out, how to create this setup, with 1 setup only?

Sometime Moq is always the best tool for the job. Try using a simple stub instead. I am still looking for a way to achieve it in Moq but this should work for now.

[TestClass]
public class UnitTest {
    [TestMethod]
    public void _Mock_Action_T_Without_Moq() {
        //Arrange
        var callbacks = new Dictionary<string, dynamic>();
        var messageHubMock = new MessageHubStub(callbacks);

        //Act
        var sut = new Sut(messageHubMock.Object);

        //Assert
        Assert.IsTrue(callbacks.Count == 2);
        Assert.IsNotNull(callbacks["Refresh"]);
        Assert.IsNotNull(callbacks["Cancel"]);
    }


    public class Sut {
        public Sut(IMessageHub MessageHub) {
            MessageHub.Subscribe<RefreshMessage>("Refresh", RefreshMethod);
            MessageHub.Subscribe<CancelMessage>("Cancel", CancelMethod);
        }

        private void CancelMethod(CancelMessage obj) {
            throw new NotImplementedException();
        }

        private void RefreshMethod(RefreshMessage obj) {
            throw new NotImplementedException();
        }
    }

    public class MessageHubStub : IMessageHub {
        private Dictionary<string, dynamic> callbacks;


        public MessageHubStub(Dictionary<string, dynamic> callbacks) {
            this.callbacks = callbacks;
        }

        public IMessageHub Object { get { return this; } }

        public void Subscribe<T>(string topic, Action<T> callback) {
            callbacks.Add(topic, callback);
        }
    }

    public interface IMessageHub {
        void Subscribe<T>(string topic, Action<T> callback);
    }

    public class MessageBase { }

    public class RefreshMessage : MessageBase { }

    public class CancelMessage : MessageBase { }
}

Also if every message is derived from MessageBase consider making it a constraint on the hub

public interface IMessageHub {
    void Subscribe<T>(string topic, Action<T> callback) where T : MessageBase;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM