-1

I have a this code

public class ClassToTest
{
    private readonly IRepository repository;

    public ClassToTest(DI GOES HERE){...}

    public DoSomething() 
    {
        Task.Run(async () => {
            //some code
            repository.ExecuteAsync();
        }
    }
}

public class Repository : IRepository
{
    public Task ExecuteAsync()
    {
        using (var connection = new SqlConnection(DbConfiguration.DatabaseConnection))
        {
            return connection.ExecuteAsync(storedProcedure, parameters, commandType: CommandType.StoredProcedure, commandTimeout: Configuration.TransactionTimeout);
        }
    }
}

[Test]
public void TestMethod()
{
    var repository = new Mock<IRepository>;
    var classToTest =  new ClassToTest();

    classToTest.DoSomething();

    repository.Veryfy(p => p.ExecuteAsync(), Times.Once());
}

The test fails with this message

Expected invocation on the mock once, but was 0 times: p => p.ExecuteAsync()

Does anyone knows why?

Thanks

Kirzy
  • 138
  • 1
  • 16
  • 3
    I suspect "DI GOES HERE" might be holding a few clues about what's going on – doctorlove Sep 26 '17 at 15:18
  • 4
    Since `DoSomething` is in fact async shouldn't you wait for the method to finish? – SteppingRazor Sep 26 '17 at 15:19
  • Test needs to be async, mock needs to be setup to be async when called. – Nkosi Sep 26 '17 at 15:30
  • 2
    Your code will not compile as `DoSomething()` has no return type. If the return type is void as its implementation suggests then `DoSomething` is fire and forget and you have no way of knowing when the method finishes as @SteppingRazor pointed out. A simple solution would be to (1) drop `Task.Run` (2) mark `DoSomething` `async` (3) `await` `ExecuteAsync` (4) write `TestMethod` as an async test. – JSteward Sep 26 '17 at 15:30

1 Answers1

5

As others have alluded, because you're calling Task.Run and not waiting for a response, the Unit test will likely complete before the background task is even started, hence the Moq Verify failure.

Also, your code won't compile as is - when asking a Q on StackOverflow, be sure to give a complete, compilable MVP.

Of special importance is the bug in the code you are trying to test. Repository.ExecuteAsync calls connection.ExecuteAsync, inside a using scope, but this isn't awaited. This will mean that the connection will be disposed before the task completes. You'll need to change the method to async and await the call to defer disposal of the connection.

The wrapper method DoSomething method shouldn't use Task.Run(), although, because it adds no value to the repository Task, it doesn't need to repeat the async / return await, either.

The caller (your Unit test, in this instance) can then await DoSomething (or if the caller genuinely wants to do further processing without awaiting the Task, then leave it to the caller to decide. At least this way, the caller gets a handle to the Task, to check on completion).

The final state of your code might look more like this:

public class ClassToTest
{
    private readonly IRepository _repository;

    public ClassToTest(IRepository repository)
    {
       _repository = repository;
    }

    // Doesn't necessarily need to be async
    public Task DoSomething() 
    {
        // We're return the wrapped task directly, and adding no additional value.
        return repository.ExecuteAsync();
    }
}

public class Repository : IRepository
{
    public async Task ExecuteAsync()
    {
        using (var connection = new SqlConnection(DbConfiguration.DatabaseConnection))
        {
            // Here we do need to await, otherwise we'll dispose the connection
            return await connection.ExecuteAsync(storedProcedure, parameters, 
              commandType: CommandType.StoredProcedure, 
              commandTimeout: Configuration.TransactionTimeout);
        }
    }
}

// NUnit has full support for async / await
[Test]
public async Task TestMethod()
{
    var repository = new Mock<IRepository>();
    var classToTest =  new ClassToTest(repository.Object);

    repository.Setup(_ => _.ExecuteAsync()).Returns(Task.FromResult((object)null));
    // Moq also has support for async, e.g. .ReturnsAsync

    // You need to await the test.
    await classToTest.DoSomething();

    repository.Verify(p => p.ExecuteAsync(), Times.Once());
}
Nkosi
  • 235,767
  • 35
  • 427
  • 472
StuartLC
  • 104,537
  • 17
  • 209
  • 285
  • Also made another edit as you have to setup the mock to return a completed task to allow the async code to flow to completion. – Nkosi Sep 26 '17 at 16:16
  • You can also use `.ReturnsAsync(...)` instead of the hacked `Task.FromResult`, unless you need the deferred execution lambda overload. – StuartLC Sep 26 '17 at 16:17
  • In which version of Moq does `ReturnsAsync` take no arguments? I've been waiting for that for a while now. – Nkosi Sep 26 '17 at 16:20
  • Another good point, OP's `Task` has no return, so of course there's no void `ReturnsAsync` :) – StuartLC Sep 26 '17 at 16:23
  • 2
    I've had to play around with my own extension methods to get that. :) – Nkosi Sep 26 '17 at 16:25