background

To test a class’s private method, which is called in a constructor,Mockito and PowerMock do not work (Mockito does not support private methods, and PowerMock does not support Junit5) There are no hard cases, “let’s see if we can solve my problem

The trial

Let’s start with a comparison of mainstream Mock tools

tool The principle of Minimum Mock unit The restriction on the Mock method To fit the difficulty IDE support
Mockito A dynamic proxy class Cannot Mock private/static and constructors More easily Very good
Spock A dynamic proxy class Cannot Mock private/static and constructors More complicated general
PowerMock Custom class loaders class Any method will do More complicated good
JMockit Runtime bytecode modification class Cannot Mock constructors (new operator) More complicated general
TestableMock Runtime bytecode modification methods Any method will do It is easy to general

This is a comparison given by TestableMock, which may be a little more home-grown, but we can see that dynamic proxy-based Mockito and Spock have limited things to do, but do not support private methods, etc. PowerMock has a custom classloader that can do a lot of things, but affects Jacoco coverage, too There is no support for Junit5(which is not bearable), while both JMockit and TestableMock are runtime modification bytecode, with the former less active in maintenance and the latter absorbing some of the design of JMockit and coming from the ari team. After the trial, it was able to meet my needs to test the private constructor invocation, and the official documentation also shows that it was very careful. Using TestableMock requires following two principles:

  1. A method that needs to be Mock out in one test case in the same test class usually needs to be Mock out in other test cases as well. Because these Mock methods often access external dependencies that are not easy to test.
  2. Each unit test focuses only on the logic inside the unit under test; irrelevant calls outside the unit should be replaced with mocks. The calls that need to be Mock should be in the code of the class under test.

Avoid pit guide

I’ve stumbled on a few things during my testing, which I think are very easy to do, even though they are explained in the official documentation and examples, so I’m going to have to put them in writing.

By default, TestableMock assumes that the Test class and the class under Test have the same package path and the name is the name of the class under Test +Test (this convention is generally true for Java projects built with Maven or Gradle). It is also agreed that the Mock container associated with the test class is either a static class inside and named Mock, or a separate class in the same package path named the name of the class under test +Mock.

TestableMock looks for Mock containers in one of two places:

  • Default location in the test class namedMockThe static inner class (such as the primitive type isDemo, the Mock container class isDemoTest.Mock)
  • Name in the same package pathClass + Mock being testedFor example, the primitive type isDemo, the Mock container class isDemoMock)

You can add a troubleshooting log for this type of problem via @mockdiagnose (loglevel.verbose) where mock Class XXX must be Found and source class XXX and Test class Found In addition, the Mock substitution only applies to the code of the class under test. In many tests, “THE Mock method was directly called in the test case, but no substitution was found”. I wasted a lot of time on this problem.

Unit test thinking

Mockito’s official advice is:

Don’t mock everything, it’s an anti-pattern

Don’t mock value objects

Don’t mock a type you don’t own!

Don’t mock classes does not belong to you, it is difficult, sometimes we want to mock third-party interface to facilitate test, Mockito hope single measurement can only focus on their own kind of logic, it is we want to reach the ultimate goal, but the reality often meet with a long history of system, have to face the legacy code, it is difficult to achieve this goal.

The Alibaba Java development manual gives the principle of unit testing as AIR

  • Automatic: Automation, unit tests should be fully automated. Test cases are usually executed on a regular basis, and the execution process must be fully automated to make sense. Do not use in unit testsSystem.outTo do the human flesh test, you have to useassertTo verify the
  • Independent: independence. In order to ensure that unit tests are stable and easy to maintain, unit test cases must not call each other or depend on the order of execution
  • RepeatableRepeatability. Unit tests can be performed repeatedly and cannot be affected by external circumstances

With unit testing, make sure the test granularity is small enough to help pinpoint the problem. Single test granularity is at most at the class level and generally at the method level. Only when the test granularity is small can the error location be located as soon as possible. Single test is not responsible for examining cross-class or cross-system interaction logic, which is the domain of integration testing

A lot of times single tests are not easy to write because the code is poorly designed, and you often turn to PowerMock or TestableMock, which do break the above rules, but are forced to do so in the face of legacy problems. In any case, we need to set goals and have a clear understanding of what makes a good single test. In my opinion, good single tests are small enough, layered, and white box.