I'm mocking some implementation using Moq and I want verify a method is called correctly on this interface, the problem is it looks something like this:
public interface IToBeMocked {
void DoThing(IParameter parameter);
}
public interface IParameter {
Task<string> Content { get; }
}
So I set up my mock:
var parameter = "ABC";
var mock = new Mock<IToBeMocked>();
mock
.Setup(m => m.DoThing(It.IsAny<IParameter>()))
.Callback<IParameter>(p async => (await p.Content).Should().Be(parameter));
new Processor(mock.Object).Process(parameter);
mock
.Verify(m => m.DoThing(It.IsAny<IParameter>()), Times.Once);
Unfortunately, this test already passes with the following implementation:
public class Processor {
public Processor(IToBeMocked toBeMocked){
_toBeMocked = toBeMocked;
}
public void Process(string parameter){
_toBeMocked.DoThing(null);
}
}
Because the Callback is async but the signature requires an Action, meaning the awaiter is never awaited, and the test ends before the exception is thrown.
Is there any functionality in Moq to await an asynchronous callback?
There seems to be some confusion. I hope this clarifies the problem.
I'm doing TDD. I've implemented the simplest shell of the code to make the test compile. Then I have written the test to ensure "ABC" is the result of the Task
and I've set the test running. It's passing. This is the issue. I want the test to fail, so I can implement the 'real' implementation.
The more I think about this the more I think this is probably impossible. I've opened an issue for the repo: https://github.com/moq/moq4/issues/737 but I was thinking about the implementation of such a feature as I was writing the request in anticipation of submitting a PR, and it seems impossible. Still if anyone has any ideas I'd love to hear them either here or in the GitHub issue and I'll keep both places up to date. For now, I suppose I will have to use a stub.
The call back expects an Action, you attempt to perform an async operation in said callback which boils down to async void
call. Exceptions cannot be caught in such situations as they are fire and forget.
Reference Async/Await - Best Practices in Asynchronous Programming
So the problem is not with the Moq framework but rather the approach taken.
Use the call back to get the desired parameter and work from there.
Review the progression of the following tests to see how the TDD approach evolves with each test.
[TestClass]
public class MyTestClass {
[Test]
public void _DoThing_Should_Be_Invoked() {
//Arrange
var parameter = "ABC";
var mock = new Mock<IToBeMocked>();
mock
.Setup(m => m.DoThing(It.IsAny<IParameter>()));
//Act
new Processor(mock.Object).Process(parameter);
//Assert
mock.Verify(m => m.DoThing(It.IsAny<IParameter>()), Times.Once);
}
[Test]
public void _Parameter_Should_Not_Be_Null() {
//Arrange
IParameter actual = null;
var parameter = "ABC";
var mock = new Mock<IToBeMocked>();
mock
.Setup(m => m.DoThing(It.IsAny<IParameter>()))
.Callback<IParameter>(p => actual = p);
//Act
new Processor(mock.Object).Process(parameter);
//Assert
actual.Should().NotBeNull();
mock.Verify(m => m.DoThing(It.IsAny<IParameter>()), Times.Once);
}
[Test]
public async Task _Parameter_Content_Should_Be_Expected() {
//Arrange
IParameter parameter = null;
var expected = "ABC";
var mock = new Mock<IToBeMocked>();
mock
.Setup(m => m.DoThing(It.IsAny<IParameter>()))
.Callback<IParameter>(p => parameter = p);
new Processor(mock.Object).Process(expected);
parameter.Should().NotBeNull();
//Act
var actual = await parameter.Content;
//Assert
actual.Should().Be(expected);
mock.Verify(m => m.DoThing(It.IsAny<IParameter>()), Times.Once);
}
}