简体   繁体   中英

Moq setup for interface with action/function argument

I am trying to mock a call to a server and verify that the tested code called the correct method. Code structure is as follows:

public interface IServerAdapter
{
    void CallServer(Action<IServerExposingToClient> methodToCall, out bool serverCalled);
    object CallServer(Func<IServerExposingToClient, object> methodToCall, out bool serverCalled);
}

public interface IServerExposingToClient
{
    Resource GetResource(string id);
    void AddResource(Resource resource);
}

The code I'm testing accesses an implementation of IServerAdapter and calls IServerExposingToClient methods on the server. The reason for this is that we don't want to have to implement every single method (there are quite a lot of them) on the IServerExposingToClient in a class. Since this is then invoked on the proxy, it works quite well in our code.

Calling a method on the server is done like this:

_mainViewModel.ServerAdapter.CallServer(m => m.AddResource(resource), out serverCalled);

The problem now is testing and mocking. I need to assert that the method ( AddResource ) has been called on the server. The out serverCalled (problem 1) is to make sure the call has made it to the server so logic flows as it should for the caller.

When I use the following Moq setup, I can assert that some method has been called on the server:

Mock<IServerAdapter> serverAdapterMock = new Mock<IServerAdapter>();
bool serverCalled;
bool someMethodCalled = false;
serverAdapterMock.Setup(c => c.CallServer(It.IsAny<Action<IServerExposingToClient>>(), out serverCalled)).Callback(() => someMethodCalled = true);
// assign serverAdapterMock.Object to some property my code will use
// test code
Assert.IsTrue(someMethodCalled, "Should have called method on server.");

As the test says, I can assert that some method has been called on the server. The problem is that I don't know which method has been called. In some methods, the logic I want to test could take different paths depending on conditions, and for some scenarios, several paths might lead to a server call. Because of this, I need to know which method was called so I can have the correct asserts in my test (problem 2).

The variable serverCalled needs to be set to match a signature for the method call on the IServerAdapter mock. I would also very much like to be able to set that variable inside some callback or whatever which would let the logic of the code I'm testing flow the way it should (problem 1). Because of the way it works now, serverCalled will not be set by the mock setup.

The main problem is not knowing which method was called against the server. I've tried to use Match to check the name of the delegate method, but no sigar.

serverAdapterMock.Setup(c => c.CallServer(Match.Create<Action<IServerExposingToClient>>( x => IsAddResource(x)), out serverCalled)).Callback(() => someMethodCalled = true);

When IsAddResource is called, the function does not refer to AddResource, only where it was created and what argument it is called with.

Does anyone know how to check which method was called?

For the other problem with out serverCalled , you could argue that the void method could be bool instead, and that the other method could return null if we don't have a connection to the server (the last argument would be ambiguous as null could either mean object did not exist, or server was unavailable).

I would very much like suggestions for working out both problems.

Thanks in advance

Problem one:

As you've pointed out, a Callback here would be ideal. Unfortunately, because your method signature has a out parameter it currently cannot be intercepted by Moq. There's a post in the Moq forums that is similar to your concern. There's no indication that it will be addressed.

If you're open to changing your method signature, consider dropping the out parameter -- they're a bit of a smell anyway. Things would be a lot simpler if you just threw an exception when the server was not available. Getting that out of there (pun intended) would open your Callback to handle your second problem.

Problem two:

Consider changing your method signature to be Expression<Action<IServerExposingToClient>>. I realize there's a lot of angle brackets there, but an Expression is syntactically equivalent to Action<T> (you wouldn't have to change your calling code) and would allow your Callback to walk the code expression tree and get the name of your calling method.

You can get the name of the method using something like:

public string GetMethodName(Expression<Action<IServerExposingToClient>> exp)
{
    return ((ExpressionMethodCall)exp.Body).Method.Name;
}

You now have enough arsenal to go after your mock. You can either write a callback that logs the names of method calls or you can write matchers to help define behavior when methods are called.

For example:

[Test]
public void WhenTheViewModelIsLoaded_TheSystem_ShouldRecordOneNewResource()
{
   // arrange
   var serverAdapterMock = new Mock<IServerAdapter>();
   var subject = new SubjectUnderTest( serverAdapterMock.Object );

   // act
   subject.Initialize();

   // assert
   serverAdapterMock.Verify( c => c.CallServer( IsAddResource() ), Times.Exactly(1));
}

static Expression<Action<IServerExposedToClient>> IsAddResource()
{
    return Match.Create<Expression<Action<IServerExposedToClient>>>(
              exp => GetMethodName(exp) == "AddResource");
}

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