Reputation: 3715
I have a method with the following line: await Task.Delay(waitTime).ConfigureAwait(false);
I there a good strategy to avoid actually waiting the few seconds when unit testing and instead verify that we tried to wait a specific number of seconds.
For instance, is there a way to inject an additional parameter into my method like in this (contrived) example where I inject a mocked object of a fictitious ITaskWaiter
interface:
// Arrange
var mockWait = new Mock<ITaskWaiter>(MockBehavior.Strict);
mockWait.Setup(w => w.Delay(It.Is<TimeSpan>(t => t.TotalSeconds == 2)));
// Act
myObject.MyMethod(mockWait.Object);
// Assert
mockWait.Verify();
Upvotes: 14
Views: 7523
Reputation: 20579
The accepted answer is the correct approach.
If you want to "hide" the dependency from client code (in different assemblies), but allow tests to use it:
public class ClassUnderTest
{
private readonly IAsyncDelayer asyncDelayer;
public ClassUnderTest() : this(new AsyncDelayer()) { } // used by client code
internal ClassUnderTest(IAsyncDelayer asyncDelayer) // used by tests
{
this.asyncDelayer = asyncDelayer;
}
public async Task<int> MethodUnderTest()
{
await asyncDelayer.Delay(TimeSpan.FromSeconds(2));
return 5;
}
}
If you don't want to introduce the mockable dependency (for whatever reason), then extract the delay to a virtual
method and mock that:
public class ClassUnderTest
{
internal virtual Task Delay(TimeSpan delay) => Task.Delay(delay);
public async Task<int> MethodUnderTest()
{
await Delay(TimeSpan.FromSeconds(2));
return 5;
}
}
In the test, you need to partial mock the class. Then mock Delay()
to return Task.CompletedTask
so it does nothing. You can also verify that the mocked method was called with the value 2
.
Upvotes: 0
Reputation: 27861
You can define a "delayer" interface like this:
public interface IAsyncDelayer
{
Task Delay(TimeSpan timeSpan);
}
And then you can provide the following implementation for production code:
public class AsyncDelayer : IAsyncDelayer
{
public Task Delay(TimeSpan timeSpan)
{
return Task.Delay(timeSpan);
}
}
Now, your class would look something like this:
public class ClassUnderTest
{
private readonly IAsyncDelayer asyncDelayer;
public ClassUnderTest(IAsyncDelayer asyncDelayer)
{
this.asyncDelayer = asyncDelayer;
}
public async Task<int> MethodUnderTest()
{
await asyncDelayer.Delay(TimeSpan.FromSeconds(2));
return 5;
}
}
This is basic application of Dependency Injection. Basically, we extracted the logic of asynchronously waiting to a different class and created an interface for it to enable polymorphism.
In production, you would compose your object like this:
var myClass = new ClassUnderTest(new AsyncDelayer());
Now, in your test you can create a fake delayer that returns immediately like this:
[TestMethod]
public async Task TestMethod1()
{
var mockWait = new Mock<IAsyncDelayer>();
mockWait.Setup(m => m.Delay(It.IsAny<TimeSpan>())).Returns(Task.FromResult(0));
var sut = new ClassUnderTest(mockWait.Object);
var result = await sut.MethodUnderTest();
Assert.AreEqual(5, result);
}
Upvotes: 26