Unit Testing
Java
Standards and Guidelines
jUnit
Test Classes
- Must be located under
src/test/java
as per Maven convention. - Must be in the same package as the class they are testing.
Must have the same name as the class they are testing and be suffixed with
Test
, e.g.,MyClassTest.java
.- Exception: If a class requires parametrized tests, multiple test classes can exist for a single class.
Test Methods
- Class setup method (
@BeforeClass
) must be namedsetupClass().
- Test setup method (
@Before
) must be namedsetup().
- Class clean up method (
@AfterClass
) must be namedtearDownClass().
- Test clean up method (
@After
) must be namedtearDown().
- Test methods can optionally be prefixed with
test
, .e.g,testMultiplyPositiveNumbers()
. - Must follow the usual method naming conventions, i.e., first letter lowercase, rest of name camel-cased.
- Must have a name that clearly describes what is being tested, e.g.,
testRequestIsRejectedWhenCredentialsAreMissing().
- When a method is difficult to name properly or test a complex scenario, proper JavaDoc comments should be added to the test method.
Differences with Spock
The above guidelines do not all apply when writing Spock tests.
For more information please see the Spock Primer
Spock
Test Classes
- Must be located under
src/test/groovy
as per Maven convention. - Must have the same name as the class they are testing and be suffixed with Spec, e.g.,
MyClassSpec.groovy
.
Test Methods
- Please see the aforementioned Spock Primer for test method documentation.
Best Practices
General
- Test fixtures that are common to all the tests in a test class and need to be initialized only once should be put inside the
@BeforeClass
method. - Test fixtures that are common to all the tests in a test class and need to be initialized before every test is run should be put inside the
@Before
method. - Clean up code that needs to be run after every test method should be put inside the
@After
method. - Clean up code that needs to be run only once after all tests have been run should be put inside the
@AfterClass
method. - Minimize the use of private inner-classes (anonymous or named) as they usually encapsulate a separate responsibility and could be extracted and unit tested on their own.
- Unit tests should test all exception scenarios, including checked and unchecked exceptions thrown by themselves or their dependencies.
See Testing Best Practices for other general best practices.
Assertions
- Favor jUnit's
assertThat()
and hamcrestMatchers
or AssertJ over plain jUnit assert methods such asassertTrue
,assertEquals
, etc. (see http://junit.sourceforge.net/doc/ReleaseNotes4.4.html for the rationale). - If you use jUnit's
assert
methods, consider providing a message to make the assertion failure clearer, e.g.,assertEquals("Unexpected multiplication result", 4, multiplcator.multiply(2, 2));
- A test method should only test one specific behavior and only contain assertions related to that behavior.
- Too many assertions is usually an indication that the test is testing too many behaviors.
- Validating too many things inside the same test method makes it difficult to properly name the test method and determine exactly what went wrong when the test fails.
When testing that an exception should be thrown, use
@Test(expected=<ExceptionClass.class>)
or theExpectedException
rule and let the jUnit runner fail the test. Do not use atry/catch
block in your code and callfail()
if the exception isn't thrown.
API documentation and unit tests
Documentation (JavaDoc) and unit tests should always be in-sync and support each other.
- Unit test every constraint or exception documented in the class' API (public and protected methods).
Document every constraint and exception covered by the unit test.
Mock Objects
- Production code should avoid
final
methods and classes as well asstatic
methods whenever possible.- Rationale: Those cannot be mocked with regular mocking frameworks such as Mockito or Spock and require the use of tools such as PowerMock.
- Mock objects should always be created using a mocking framework, never written by hand.
- Rationale: Writing mock object by hand may require complex code to mock certain behaviors, which increases the chance of introducing bugs in the mocking code and the maintenance cost of the code base.
- All the dependencies of a class should be replaced with mock objects before the test is run.
- Exception: Value objects and beans that do not have any behavior do not have to be mocked.
- Exception: Dependencies used by legacy code do not need to be replaced with mock objects if doing so requires too much effort or introduces too much risk.
- When verifying a mock object's expectations, expect specific arguments. In other words, avoid argument matchers such as
any()
,anyList()
, etc.- Rationale: The goal of verifying expectations is to ensure that a class respects the contract it has with its dependencies. Using
any...()
doesn't enforce this. - Exception: If the value does not support the current test case and is tested in another unit test.
- Rationale: The goal of verifying expectations is to ensure that a class respects the contract it has with its dependencies. Using
- Favor
argThat()
orArgumentCaptor
over customMatcher
to validate argument values- Rationale: Custom
Matcher
s are usually more difficult to write, read and maintain than using simpleargThat()
orArgumentCaptor
s with assertions.
- Rationale: Custom
- Avoid the use of Mockito's
thenAnswer()
anddoAnswer()
to validate input parameters, useArgumentCaptor
instead- Rationale: those methods are meant to setup mock expectations, not verify their behavior.
- Exception: When the response to return needs to be built from the parameters provided to the mocked method.
- Exception: When other mocking techniques cannot be used to verify expectations, e.g., output parameters need to be verified.
Exception: When the code delegates to
Callable
,Runnable
or any other kind ofFunctionalInterface
.
- Only verify conditions that are related to the behavior being tested. Over-verification makes tests brittle and break for no good reason.
- Example: If a mock has a method that is currently called only once but could be called multiple times without impacting the result of the test, do not fail the test (e.g.,
verify(mockObject, times(1)).expectedMethod()
) if it is called more than once.
- Example: If a mock has a method that is currently called only once but could be called multiple times without impacting the result of the test, do not fail the test (e.g.,
- Mocking tools such as PowerMock should only be used to unit test complex legacy code that cannot be easily refactored.
- Exception: An acceptable use of PowerMock outside of legacy code testing is when a class needs to use a system or third-party class' static method. This is especially useful when testing exception scenarios that would otherwise be difficult or impossible to test. It can also help prevent dependencies on the environment itself, one of the basic qualities of a good unit test.
Dependency Injection
Leveraging dependency injection provided by tools such as Blueprint is a great way to make writing unit tests easier (see Mock Objects section above).
- Production classes should be written in such a way that their dependencies are injected through their constructors or setter methods and not created or looked-up from within the classes themselves.
- Prefer dependency injection over static methods in production classes.
- Rationale: Static methods create a tight coupling between a class and its dependencies. This makes it difficult to test the class in isolation or test edge-cases, such as the static method throwing an exception or returning a specific value.
When a dependency cannot be injected (e.g., multiple instances of the same dependency are needed within the class), consider creating and injecting a factory class or a
Supplier
, or use the Factory Method design pattern.- When testing concurrent classes that use a thread
Executor
, useMoreExecutors.directExecutor()
to inject anExecutor
that runs in the testing thread and eliminate any issues associated with tested concurrent code. - Similarly, when testing concurrent classes that use an
ExecutorService
, replace it withMoreExecutors.newDirectExecutorService()
.
Code Coverage
- Getters and setters should not be tested unless they contain some logic.
- Aim for 100% code coverage and always set or update the thresholds in the module's pom file to match the actual code coverage (minus 2% to account for rounding errors).
- Coverage must have at least 75% at the bundle level for instruction, line and path coverage as well as cyclomatic complexity.
- Exception: These thresholds can be lower if the code contains many getters and setters, conditional logging statements or try-with-resources blocks.
- Exception: Legacy code does not have to hit the 75% mark when committed.
- Coverage thresholds should always go up, unless the new code added only contains getters and setters or logging statements
- Rationale: The goal is to increase those thresholds over time and reach 75-80% overall coverage in the future; lowering the numbers goes against that goal.
- Write tests that cover exceptions thrown by a class' dependencies.
Example: If a dependency can throw an
IllegalArgumentException
, a test should exist to ensure that the exception is properly handled, even if the behavior is to let the exception bubble up.Methods that throw exceptions
Jacoco will not mark methods that do not return (ie. methods that throw Runtime Exceptions) as having been covered, even if they are. If there is a case where a helper method constructs and throws an exception, it will better satisfy Jacoco to have that method return the created exception that the caller can then throw.
Ensure that all paths in a
stream
orOptional
are covered, including allfilter
,nonNull
, etc.Streams
By default, JaCoCo will consider a
stream
as a single path and mark all the lines as covered as long as the line has been executed once. This means that actual path coverage may not be complete.Same comment applies to
Optional.orElse
methods.
Smells
- Slow unit tests
- Slow unit tests is usually an indication that the test uses a slow external dependency. In those cases, the test should be changed to mock out that dependency.
- Unit tests that need wait for a multi-threaded class to complete it's work before doing assertion and verification is another typical example. In those cases, mocking out the threaded out code or injecting direct executors (see above) usually helps solve those problems.
Anti-Patterns
General
- The visibility of
private
methods should not be changed only to allow their internal logic to be tested.- Rationale: Making a method non-private breaks a class' encapsulation and couples the test with the class' implementation details.
- Exceptions: Constructors, setters or factory methods made package-private so they can be used to inject mock dependencies are acceptable but should only be used as a last resort.
See Testing Anti-Patterns for other general testing anti-patterns.