Testable Design and Mocking - aapowers/BroadleafCommerce GitHub Wiki

Testable Design and Mocking

Testable Design

Testable design is writing, designing, and building code in a way that makes it easy to test that code. Some aspects of testable design include modularized code that can be tested as an individual module, accessible methods, and adequate dependency injections. Some goals of testable design include being able to focus testing on one unit by faking elements that are implemented and tested elsewhere and being able to more easily force tests down all branches or paths.

The following are examples that make it difficult to test code:

  • complicated private methods
  • hard-coded object constructor calls (not using dependency injection)
  • logic in a constructor
  • singletons

Generating a secure password

In the BroadleafCommerce profile core service, there is a function that handles sending an email to a user if they forget their password. This function ends up calling a static method to generate a new secure password. Unfortunately, we cannot fully test the rest of the function as it is written because we do not have a way to know what the generated password was, so we cannot verify that it gets moved along in the function correctly.

By changing the password generator to a non-static function and using dependency injection to pass it into the email function, we are able to mock the password generator and choose the password that gets generated, so we know what the value is. (Mocking will be covered more thoroughly in the following section).

The revised code and new tests are available on GitHub. (Please note that this code is on a separate branch, because it is for demonstration purposes only. It contains duplicated functions in an attempt to leave the original source code that is used throughout the BroadleafCommerce application untouched.)

Mocking

Mocking is a really powerful way of creating a fake object that can be used in place of a real object in a unit test. The mock object allows for stubbing (forcing a certain response from the mock object when it receives a specific function call with specific input), but also allows for verification on object interaction. The mock object can be specified to do nothing, throw an exception, or return a variable when it is called upon. It can then verify that the calls made on it were in the expected order. This allows us to not only verify that we received the response we expected at the end of our unit test, but can also ensure that each step along the way happens in the expected order. This could end up revealing a bug in our code's interaction that might not show up in the output of the specific function that we are testing.

In the previous examples, specifically when we discussed code coverage in BroadleafCommerce's Profile Core Services, we used Mockito mocks in order to stub fake objects, but we did not use them to verify object interaction. This means that while all of the tests pass, the interactions between objects could still be wrong. We need to add additional verification on our current mock objects to ensure that they are called in the expected order the expected number of times.

Examples of tests that were improved through the verification of object interaction:

  • In CustomerAddressServiceImplTest and others, we previously wrote tests for void functions, but were unable to verify much about them, because they did not return anything. By adding interaction verification on the existing mocks, we now can verify that the mock objects received the expected function calls with the expected inputs the expected number of times. This gives us greater confidence in the quality of the void function despite not receiving a final output to verify against.
  • In CustomerServiceImplTest, CustomerPaymentServiceImplTest, CustomerAddressServiceImplTest, CustomerPhoneServiceImplTest and others, we previously wrote tests that made a series of mock calls. However, we had no way to verify that each call along the way was successful. We really only verified that the final call was successful. By adding InOrder verification checks on the existing mock objects, we are now able to verify that the mock objects are called in the correct order. This gives us greater confidence in the quality of the code, because we verify that each call is made successfully, and we verify that certain checks happen before others.

The revised tests can be found on GitHub.

Next section: Static Analyzers

⚠️ **GitHub.com Fallback** ⚠️