Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does application layer unit tests look like in DDD?

In my job we are writing web services, which are called by an app. We are working in agile mind set using domain driven design. As in DDD we have domain and application layer. However, we encountered problem in writing unit tests for those layers, because it seems that we are testing domain logic twice: in domain unit tests and again in application unit tests:

Application unit test

    [TestMethod]
    public void UserApplicationService_SignOut_ForExistingUserWithBalance_ShouldClearBalanceAndSignOut()
    {
        //Arrange
        long merchantId = 1;
        long userId = 1;

        var transactionId = "001";
        var id = "122";            
        var user = Help.SetId<User>(User.Register(id, new DateTime(2015, 01, 01, 00, 00, 01)), userId);

        _usersDb.Add(user);
        var userBonusBalanceRepository = _testContext.MoqUnitOfWork.MockUnitOfWork.Object.GetUserBonusAccountRepository();

        UserBonusAccount uba = userBonusBalanceRepository.GetForUser(user);
        uba.PayTo(
            new Domain.Core.Money { TotalAmount = 10, BonusAmount = 0 },
            new Domain.Core.Outlet
            {
                BonusPercentage = 50,
                IsLoyalty = true,
                Id = id,
                OutletId = "111"
            },
            transactionId,
            DateTime.Now);
        userBonusBalanceRepository.Update(uba);          

        //Act
        _testContext.UserApplicationService.SignOut(id);

        //Assert
        var firstOrDefault = this._balances.FirstOrDefault(x => x.UserId == user.Id && x.MerchantId == merchantId);
        Assert.IsTrue(firstOrDefault != null && firstOrDefault.Balance == 0);
        Assert.IsNotNull(this._transactions.Where(x => x.Id == transactionId && x.Type == BonusTransactionType.EraseFunds));
    }

Domain unit test

    [TestMethod]
    public void UserBonusAccount_ClearBalances_shouldClearBalancesForAllMerchants()
    {
        long userId = 1;
        long firstMerchantId = 1;
        long secondMerchantId = 2;
        User user = User.Register("111", new DateTime(2015, 01, 01, 00, 00, 01));
        Shared.Help.SetId(user, userId);
        List<BonusTransaction> transactions = new List<BonusTransaction>();
        List<BonusBalance> balances = new List<BonusBalance>();

        var userBonusAccount = UserBonusAccount.Load(transactions.AsQueryable(), balances.AsQueryable(), user);

        userBonusAccount.PayTo(new Money {TotalAmount = 100, BonusAmount = 0},
            new Outlet
            {
                BonusPercentage = 10,
                IsLoyalty = true,
                MerchantId = firstMerchantId,
                OutletId = "4512345678"
            }, "001", DateTime.Now);

        userBonusAccount.PayTo(new Money {TotalAmount = 200, BonusAmount = 0},
            new Outlet
            {
                BonusPercentage = 10,
                IsLoyalty = true,
                MerchantId = secondMerchantId,
                OutletId = "4512345679"
            }, "002", DateTime.Now);

        userBonusAccount.ClearBalances();

        Assert.IsTrue(userBonusAccount.GetBalanceAt(firstMerchantId) == 0);
        Assert.IsTrue(userBonusAccount.GetBalanceAt(secondMerchantId) == 0);
    }

As you can see these both tests checks whether user balance is 0, which is domain responsibility. Thus the question is: how application layer unit tests should look like and what it should test? Somewhere I read that unit tests should test in "application services for flow control and domain models for business rules". Could someone elaborate more and give some examples what application layer unit tests should test and look like?

like image 824
M. Dabulskis Avatar asked Dec 28 '15 08:12

M. Dabulskis


People also ask

What is the application layer in DDD?

The application layer Application Layer: Defines the jobs the software is supposed to do and directs the expressive domain objects to work out problems. The tasks this layer is responsible for are meaningful to the business or necessary for interaction with the application layers of other systems.

How do you test for DDD?

To diagnose DDD, you may need to have some imaging tests. You may have an x-ray, which can help your surgeon "see" the bones in your spine. X-rays are effective at showing narrowed spinal channels (spinal stenosis), fractures, bone spurs (osteophytes), or osteoarthritis.

What is application unit testing?

Unit testing is a software development process in which the smallest testable parts of an application, called units, are individually and independently scrutinized for proper operation. This testing methodology is done during the development process by the software developers and sometimes QA staff.

Should you unit test Data Access Layer?

It is a good practice to write unit test for every layer, even the DAL.


1 Answers

App Service Unit Tests

The responsibilities of app services include input validation, security and transaction control. So this is what you should test!

Here are some example questions that app service unit tests should provide and answer for:

Does my app service...

  • behave correctly (e.g. return the expected error) when I pass in garbage?
  • allow only admins to access it?
  • correctly commit the transaction in the success case?

Depending on how exactly you implement these aspects it may or may not make sense to test them. Security, for example, is often implemented in a declarative style (e.g. with C# attributes). In that case you may find a code review approach more appropriate than checking the security attributes of each and every app service with a unit test. But YMMV.

Also, make sure your unit tests are actual unit tests, i.e. stub or mock everything (especially domain objects). It's not clear in your test that this is the case (see side note below).

Testing Strategies for App Services in General

Having unit tests for app services is a good thing. However, on the app service level, I find integration tests to be more valuable in the long run. So I typically suggest the following combined strategy for testing app services:

  1. Create unit tests for the good and bad cases (TDD style if you want to). Input validation is important, so don't skip the bad cases.
  2. Create an integration test for the good case.
  3. Create additional integration tests if required.

Side Note

Your unit tests contain a few code smells.

For example, I always instantiate the SUT (system under test) directly in unit tests. Like that, you exactly know what dependencies it has, and which of them are stubbed, mocked, or the real one is used. In your tests, this is not at all clear.

Also, you seem to depend on fields for collecting the test output (this._balances for example). While this is usually not a problem if the test class contains only a single test, it can be problematic otherwise. By depending on fields, you depend on state that is "external" to the test method. This can make the test method difficult to understand, because you can't just read through the test method, you need to consider the whole class. This is the same problem that occurs when over-using setup and tear-down methods.

like image 177
theDmi Avatar answered Nov 15 '22 09:11

theDmi