Unit Tests are useful to keep an eye on the components' implementation. The legacy philosophy of Spring promotes reusable components application-wide. The core implementations of these components may either alter states (states of transitory objects) or trigger interactions with other components.
Using Mocks in Unit Tests specifically assesses the behavior of component's methods in regard to other components. When the developer gets used to Mocks, it is amazing to see how much the design becomes influenced toward the use of different layers and logic externalization. Similarly, object names and method names are given more importance. Because they summarize something that is happening elsewhere, Mocks save the energy of the next developer that will have to operate in the area of code.
Developing Unit Tests is by definition an Enterprise policy. As the percentage of code covered by tests can easily reflect the maturity of a product, this code-coverage rate is also becoming a standard reference to assess companies in regard to their products. It must also be noted that companies practicing code reviews as a development process find valuable insights from Pull Requests. When Pull Requests highlight behavioral changes through tests, the impact of potential changes becomes clear faster.
Maven Install
on the cloudstreetmarket-parent
project as in the previous recipe. When the build process comes to build the core module, you should see the following logs that suggest the execution of unit tests during the test phase (between compile and package):cloudstreetmarket-core
module, specifically in the src/test/java
source folder:Both unit tests and integration tests use JUnit:
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.9</version> </dependency>
IdentifiableToIdConverterTest
(see the following code). This class asserts that all the registered Entities can be converted by IdentifiableToIdConverter
for being Identifiable
implementations (remember HATEOAS:):import static org.junit.Assert.*; import org.junit.Test; import edu.zipcloud.cloudstreetmarket.core.entities.*; public class IdentifiableToIdConverterTest { private IdentifiableToIdConverter converter; @Test public void canConvertChartStock(){ converter = new IdentifiableToIdConverter(ChartStock.class); assertTrue(converter.canConvert(ChartStock.class)); } @Test public void canConvertAction(){ converter = new IdentifiableToIdConverter(Action.class); assertTrue(converter.canConvert(Action.class)); } }
YahooQuoteToCurrencyExchangeConverterTest
:@RunWith(MockitoJUnitRunner.class) public class YahooQuoteToCurrencyExchangeConverterTest { @InjectMocks private YahooQuoteToCurrencyExchangeConverter converter; @Mock private CurrencyExchangeRepository currencyExchangeRepository; @Test public void transferCriticalData(){ when(currencyExchangeRepository.findOne( any(String.class)) ) .thenReturn(new CurrencyExchange("WHATEVER_ID"")); CurrencyExchange currencyExchange = converter.convert(buildYahooQuoteInstance()); assertEquals("WHATEVER_ID"",currencyExchange.getId()); assertEquals("USDGBP=X"", currencyExchange.getName()); assertEquals(BigDecimal.valueOf(10), currencyExchange.getBid()); ... assertEquals(BigDecimal.valueOf(17), currencyExchange.getOpen()); verify(currencyExchangeRepository, times(1)) .findOne(any(String.class)); } ... }
Here, the highlighted transferCriticalData()
test gets an instance of YahooQuoteToCurrencyExchangeConverter
that is not initialized with a real @Autowired CurrencyExchangeRepository
but instead with a Mock. The converter gets its convert()
method invoked with a YahooQuote
instance.
<dependency> <groupId>org.mockito</groupId> <artifactId>mockito-all</artifactId> <version>1.9.5<version> </dependency>
CommunityServiceImplTest.
For example, in the following example, the registerUser_generatePasswordAndEncodeIt
test makes use of the ArgumentCaptor
:@Test public void registerUser_generatesPasswordAndEncodesIt() { when(communityServiceHelper.generatePassword()) .thenReturn("newPassword"); when(passwordEncoder.encode("newPassword")) .thenReturn("newPasswordEncoded"); ArgumentCaptor<User>userArgumentCaptor = ArgumentCaptor.forClass(User.class); userA.setPassword(null); communityServiceImpl.registerUser(userA); verify(userRepository, times(1)) .save(userArgumentCaptor.capture()); verify(passwordEncoder, times(1)) .encode("newPassword"); String capturedGeneratedPassword = userArgumentCaptor.getValue().getPassword(); assertEquals("newPasswordEncoded", capturedGeneratedPassword); }
The @Test
annotation must be placed on public void methods so that JUnit considers them as test cases. An exception thrown within one of these methods will be considered as a test failure. Consequently, an execution without any exception thrown represents a success.
The @Test
annotation can be customized, passing the following two optional arguments.
An expected parameter on an @Test
annotation specifies that the test is expected to throw a specific type of exception to be successful. When a different Type of exception is thrown or when no exception is thrown at all, JUnit must consider the execution as a failure. When a test case is provided a timeout parameter in its @Test
annotation, this test will fail when the execution lasts more than the indicated time.
As introduced in the recipe, the @RunWith
annotation permits the use of external test runners (instead of the default BlockJUnit4ClassRunner
coming with JUnit). By the way, a declarative technique for specifying the default JUnit runner could be to get @RunWith
targeting JUnit4.class
like so: @RunWith(JUnit4.class)
.
A runner runs tests and notifies a | ||
--JUnit.org Javadoc |
A custom Runner
must implement abstract methods from org.junit.runner.Runner
such as run(RunNotifier notifier)
and getDescription()
. It must also follow up on core JUnit functions, driving for example, the test execution flow. JUnit has a set of annotations such as @BeforeClass
, @Before
, @After
, and @AfterClass
natively handled by org.junit.runner.ParentRunner
. We are going to visit these annotations next.
In test classes that contains several test cases, it is a good practice to try making the test logic as clear as possible. From this perspective, variable initialization and context reinitialization are operations that people often attempt to externalize for reusability. @Before
annotations can be defined on public void
methods to get them executed by the Runner before every single test. Similarly, @After
annotations mark the public void
method again to be executed after each test (usually for cleanup resources or destroying a context).
For information, on inheritance, @Before
methods of parent classes will be run before those of the current class. Similarly, @After
methods declared in superclasses will be run after those of the current class.
Another interesting point from the Javadoc specifies that all @After
methods are guaranteed to run, even if a @Before
or a @Test
annotated method throws an exception.
The
@BeforeClass
and @AfterClass
annotations can be applied to public static void methods. @BeforeClass
causes a method to be run once in the test life cycle. The method will be run before any other @Test
or @Before
annotated methods.
A method annotated @AfterClass
is guaranteed to be run once after all tests and also after all @BeforeClass
, @Before,
or @After
annotated methods even if one of them throws an exception.
@BeforeClass
and @AfterClass
are valuable tools for handling performance-consuming operations related to the preparation of test context (database connection management and pre/post business treatments).
For information, on inheritance, @BeforeClass
annotated methods in superclasses will be executed before the ones of the current class, and @AfterClass
annotated methods in the superclasses will be executed after those of the current class.
Mockito is an Open Source testing framework that supports Test-Driven Developments and Behavior-Driven developments. It permits the creation of double objects (Mock objects) and helps in isolating the system under test.
We have been talking about custom runners. The MockitoJUnitRunner
is a bit particular in the way that it implements a decoration pattern around the default JUnitRunner
.
Such design makes optional the use of this runner (all the provided services could also be implemented declaratively with Mockito).
The MockitoJUnitRunner
automatically initializes @Mock
annotated dependencies (this saves us a call to MockitoAnnotations.initMocks(this)
, in a @Before
annotated method for example).
initMocks(java.lang.Object testClass)
Initializes objects annotated with Mockito annotations for given testClass: | ||
--Javadoc |
The
MockitoJUnitRunner
also validates the way we implement the framework, after each test method, by invoking Mockito.validateMockitoUsage()
.This validation assertively gets us to make an optimal use of the library with the help of explicit error outputs.
The system under test is the YahooQuoteToCurrencyExchangeConverter
. The @InjectMocks
annotation tells Mockito to perform injection of dependencies (constructor injection, property setter, or field injection) on the targeted converter using initialized Mocks before each test.
The Mockito.when(T methodCall)
method, coupled with thenReturn(T value)
allows the definition of a fake CurrencyExchange
returned object when a call to currencyExchangeRepository.findOne
will actually be made inside the converter.convert(...)
tested method.
The Mockito verify
method with verify(currencyExchangeRepository, times(1)).findOne(any(String.class))
tells Mockito to validate how the tested convert
method has interacted with the Mock(s). In the following example, we want the convert
method to have called the repository only once.
More specifically in the registerUser_generatesPasswordAndEncodesIt
test, we make use of a MockitoArgumentCaptor
to manually perform deeper analyses on the object that a mocked method has been called with.
A MockitoArgumentCaptor
is useful when we don't have an intermediate layer and when results are reused to invoke other methods.
More introspection tools than the superficial (but still very useful) Type checking can be required (for example, any(String.class)
). An ArgumentCaptor
as a solution is used with extra local variables in test methods.
We advise the Mockito's Javadoc that is very well done and full of practical examples
http://docs.mockito.googlecode.com/hg/org/mockito/Mockito.html
We didn't cover JUnit Rules in any way so far. JUnit offers @Rule
annotations that can be applied on test-class fields to abstract recurring business-specific preparations. It is often used to prepare test context objects (fixtures).