简体   繁体   中英

C# / Moq - How to force an exception and return a value in one step

I have a repository with the following method DoSomeWork :

internal class MyRepository : IMyRepository
{
   public MyRepository(ILogger<MyRepository> logger, IDbContextWrapper dbContext)
   {
       this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
       this.dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
   }

   public Task<Result> DoSomeWork(int someInt)
   {
       return Task.Run(() =>
       {
           try
           {
               var parameters = new DynamicParameters(new { SomeIntValue = someInt });
               parameters.Add("WorkComplete", DbType.Boolean, direction: ParameterDirection.ReturnValue);
               dbContext.TransactionedExecute("NameOfStoredProcedure", parameters, CommandType.StoredProcedure); //This is a wrapper for Dapper (DbConnection)
               var status = (DoSomeWorkStatus)parameters.Get<int>("WorkComplete");
               var workComplete = status == DoSomeWorkStatus.DoneSuccessfully;

               return workComplete ? Result.WorkDone : Result.NoWorkDone;
           }
           catch(DatabaseTimeoutException dte)
           {
               logger.LogInformation(dte, "");
               return Result.Error;
           }
           catch(DatabaseDeadlockException dde)
           {
               logger.LogInformation(dde, "");
               return Result.Error;
           }
       });
    }
}

What I'm trying to achieve is to test and verify that once a DatabaseTimeoutException or DatabaseDeadlockException is caught inside the try/catch, the task should return Result.Error . And all this should happen in one step (without retry).

In the test I have the following:

private Mock<IMyRepository> myRepoMock;
private MyRepoManager target;

...

[SetUp]
public void SetUp()
{
   myRepoMock = new Mock<IMyRepository>();
   target = new MyRepoManager(myRepoMock.Object);
}

[Test]
public async Task MyMoqTest()
{
    //Arrange
    myRepoMock
      .Setup(mrm => mrm.DoSomeWork(It.IsAny<int>()))
      .Returns(Task.FromException<Result>(new DatabaseTimeoutException()));

    //myRepoMock
    //  .Setup(mrm => mrm.DoSomeWork(It.IsAny<int>()))
    //  .Throws<DatabaseTimeoutException>(); <- The same result as above

    //Act
    Result taskResult = await target.RunTask(int someInt); //Calls repository method - DoSomeWork

    //Assert
    Assert.AreEqual(Result.Error, taskResult.Result);
}

But what happens is that the repository throws the DatabaseTimeoutException without returning the Result.Error , and the test fails with the message (as expected):

MyExceptions.DatabaseTimeoutException : Exception of type 'MyExceptions.DatabaseTimeoutException' was thrown.

I'm very new to Moq, and so my question is - can this be done with Moq, and if so, how would I go about doing so?

Thanks.

The most important part of unit testing is to identify the System Under Test (SUT). That's the thing that you'll actually be verifying works. Once you've identified that, all dependencies of your SUT should be mocked, so that you can tightly control everything external to the thing you're testing.

If you're trying to unit test MyRepoManager.RunTask, then it should not care about any of the internal implementation details of its dependencies. It should only care about the contract that they expose. In this case, you have a dependency on IMyRepository. So it's irrelevant what the concrete implementation MyRepository does. MyRepository might handle DatabaseTimeoutException and DatabaseDeadlockException internally, but that's an implementation detail, not part of the contract defined via IMyRepository. The goal is to mock the behavior of the dependencies, not completely reimplement the dependencies internal behavior within a mocking framework.

So, your mock setup should be:

myRepoMock
    .Setup(mrm => mrm.DoSomeWork(It.IsAny<int>()))
    .Returns(Task.FromResult(Result.Error));

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