Search code examples
c#unit-testingmoqxunit

Unit testing state based test


Let's say I have a method called CreateApplication and a private helper method GenerateApplication which generates application object with calculated fields. After that, I am inserting the generated application object into the database.

public async Task<Guid> CreateApplication(CreateApplicationdRequest request)
{
    var application = GenerateApplication(request);

    await UnitOfWork.Application.InsertAsync(application); // it's same as _db.Application.InsertAsync(application) 
    await UnitOfWork.CommitAsync();  // it's same as _db.SaveChangesAsync();

    return application.Id;
}

What will be a proper unit test for such cases? Should I verify the InsertAsync method (which inserts application into the database) and also check if the application object was correctly generated?

Something like this:

[Fact]
public async Task CreateApplication_WhenCalled_ShouldStoreApplication()
{
    // I have already mocked unitofwork in constructor
    // when InsertAsync method will called, it will assign passed argument to declared private field (application)
    Application application;
    UnitOfWork.Setup(x => x.Application.InsertAsync(It.IsAny<Application>()))
        .Callback<Application>(app => application = app);

    //act
    await _applicationService.CreateApplication(new CreateApplicationdRequest()); // dummy request

    //assert 
    UnitOfWork.Verify(x => x.Application.InsertAsync(It.IsAny<Application>()), Times.Once);
    UnitOfWork.Verify(x => x.CommitAsync(), Times.Once);

    //here I am checking generated application's state
    Assert.Equal(application.CustomerId, request.CustomerId);
    Assert.Equal(application.Applicant.PhoneNumber, request.PhoneNumber);
    Assert.Equal(application.LoanCurrencyId, request.LoanCurrencyId);
    Assert.Equal(application.TermType, request.TermType);
    Assert.Equal(application.Term, request.Term);
    Assert.Equal("01017054322_CC-01001", application.DocumentNo);
    Assert.Equal(application.GracePeriod, request.DefaultGracePeriod);
}

Or this is not a correct approach and it's better to use integration tests for such cases?


Solution

  • There are several approaches how can unit test a method. The two most popular ones are blackbox and whitebox testing.

    Blackbox testing

    You don't know anything about the implementation. You only care about outputs (which includes return value, out parameters and side-effects). So you want to make sure whenever you call it with a given input then you would see the expected outputs.

    In that case your test would only examine the returned Guid and verify that the Application is stored in the mocked storage.

    Assert.NotEqual(result, Guid.Empty);
    Assert.NotNull(x.Application.FirstOrDefault(app => app.Id == result));
    

    This approach can be really easily extended to Integration testing, instead of using a mocked storage you could use a sandbox version of your storage.

    Whitebox testing

    You know how your method is implemented. And you want to make sure that your code does not do any unwanted, like overriding some of the properties of the Application between GenerateApplication and InsertAsync method calls.

    This can be a good or a bad depending on how you look at this. Let's suppose you need to normalize the PhoneNumber and you do this inside your CreateApplication for whatever reason. From a blackbox perspective it does not matter (no need to update your test) because the functionality did not change. From a whitebox angle you need to adjust your test to mock the normalizer and verify its call and assert the expected output.

    Former can be seen as a safety net from regression point of view, while latter can be considered as a safety net for refactoring.