src/test/java
as per Maven convention.Must have the same name as the class they are testing and be suffixed with Test
, e.g., MyClassTest.java
.
@BeforeClass
) must be named setupClass().
@Before
) must be named setup().
@AfterClass
) must be named tearDownClass().
@After
) must be named tearDown().
test
, .e.g, testMultiplyPositiveNumbers()
.testRequestIsRejectedWhenCredentialsAreMissing().
The above guidelines do not all apply when writing Spock tests. For more information please see the Spock Primer |
src/test/groovy
as per Maven convention.MyClassSpec.groovy
.@BeforeClass
method.@Before
method.@After
method.@AfterClass
method.See Testing Best Practices for other general best practices.
assertThat()
and hamcrest Matchers
or AssertJ over plain jUnit assert methods such as assertTrue
, assertEquals
, etc. (see http://junit.sourceforge.net/doc/ReleaseNotes4.4.html for the rationale).assert
methods, consider providing a message to make the assertion failure clearer, e.g., assertEquals("Unexpected multiplication result", 4, multiplcator.multiply(2, 2));
// Do not do this @Test public void testMultiply() { assertThat(multiplicator.multiply(2, 2), equalTo(4)); assertThat(multiplicator.multiply(2, -2), equalTo(-4)); assertThat(multiplicator.multiply(-2, -2), equalTo(4)); } // Do this instead @Test public void testMultiplyPositiveNumbers() { assertThat(multiplicator.multiply(2, 2), equalTo(4)); } @Test public void testMultiplyPositiveWithNegative() { assertThat(multiplicator.multiply(2, -2), equalTo(-4)); } @Test public void testMultiplyNegativeNumbers() { assertThat(multiplicator.multiply(-2, -2), equalTo(4)); } |
When testing that an exception should be thrown, use @Test(expected=<ExceptionClass.class>)
or the ExpectedException
rule and let the jUnit runner fail the test. Do not use a try/catch
block in your code and call fail()
if the exception isn't thrown.
// Do not do this @Test public void testMultiplyOverflowWrongWay() { try { multiplicator.multiply(Integer.MAX_VALUE, Integer.MAX_VALUE); fail("Expected multiplication overflow"); } catch (MultiplicationOverflowException e) { // Success } } // Do this instead @Test(expected=MultiplicationOverflowException.class) public void testMultiplyOverflowWrongWayRightWay() throws MultiplicationOverflowException { multiplicator.multiply(Integer.MAX_VALUE, Integer.MAX_VALUE); } |
Documentation (JavaDoc) and unit tests should always be in-sync and support each other.
Document every constraint and exception covered by the unit test.
// Class public class TextParser { /** * Counts the number of times a {@code token} appears in a string. * * @param text * text to search. Cannot be {@code null}. * @param token * token to look for. Cannot be empty or {@code null}. * @throws IllegalArgumentException * thrown if {@code text} is {@code null}, or if {@code token} is {@code null} or empty. */ public int countOccurences(String text, String token) throw IllegalArgumentException { Validate.notNull(text, "Text cannot be null"); Validate.notNull(token, "Token cannot be null"); Validate.notEmpty(token, "Token cannot be empty"); // Implementation } } // Unit Test public class TextParserTest { private TextParser textParser; @Before public void setup() { textParser = new TextParser(); } @Test public void testCountOccurencesFindsOneOccurence() { // Test code and assertions } @Test public void testCountOccurencesFindsNoOccurences() { // Test code and assertions } // More happy path test cases... @Test(expected=IllegalArgumentException.class) public void testCountOccurencesFailsWithNullText() { textParser.countOccurences(null, "A"); } @Test(expected=IllegalArgumentException.class) public void testCountOccurencesFailsWithNullToken() { textParser.countOccurences("Some text", null); } @Test(expected=IllegalArgumentException.class) public void testCountOccurencesFailsWithEmptyToken() { textParser.countOccurences("Some text", ""); } } |
final
methods and classes as well as static
methods whenever possible.any()
, anyList()
, etc.any...()
doesn't enforce this.argThat()
or ArgumentCaptor
over custom Matcher
to validate argument valuesMatcher
s are usually more difficult to write, read and maintain than using simple argThat()
or ArgumentCaptor
s with assertions.thenAnswer()
and doAnswer()
to validate input parameters, use ArgumentCaptor
insteadException: When the code delegates to Callable
, Runnable
or any other kind of FunctionalInterface
.
// Class public class SecureRunner implements Callable { private final Security security; public SecureRunner(Security security) { this.security = security; } public String runSecurely() { return security.runWithSubjectOrElevate(this::call); } public String call() { return "It works!" } } // Unit Test @RunWith(MockitoJUnitRunner.class) public class SecureRunnerTest { @Mock private Security security; public void testDoSomethingIsCalled() { when(security.runWithSubjectOrElevate(any(Callable.class))).thenAnswer(invocation -> ((Callable) invocation.getArguments()[0]).call()); SecureRunner secureRunner = new SecureRunner(security); String message = secureRunner.runSecurely(); assertThat(message, equalTo("It works!")); } } |
verify(mockObject, times(1)).expectedMethod()
) if it is called more than once.Leveraging dependency injection provided by tools such as Blueprint is a great way to make writing unit tests easier (see Mock Objects section above).
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.
Executor
, use MoreExecutors.directExecutor()
to inject an Executor
that runs in the testing thread and eliminate any issues associated with tested concurrent code.ExecutorService
, replace it with MoreExecutors.newDirectExecutorService()
.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.
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
or Optional
are covered, including all filter
, nonNull
, etc.
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.
private
methods should not be changed only to allow their internal logic to be tested.See Testing Anti-Patterns for other general testing anti-patterns.