Writing unit tests can be challenging. Often applications run inside an application server which provides services and a unit test runs outside of the server. Applications can depend on the services provided by the application server which makes testing outside of this scope difficult. One of those services is JNDI. JNDI makes it possible for an application to look up for example a datasource. Luckily JNDI can be provided outside of application server scope.
To make mocking methods in unit tests easier, the Mockito framework (https://code.google.com/p/mockito/) can be used. Mockito however uses a subclassing mechanism which does not work when dealing with final classes. Also static and private methods cannot be mocked because of this. PowerMock provides solutions for those issues; https://code.google.com/p/powermock/.
Implementation
First we'll create an OracleConnectionPoolDatasource which we then make available outside of application server scope via JNDI. Next there is an example how final classes, private fields/methods and statics can be mocked.
OracleConnectionPoolDatasource
We want to create an OracleConnectionPoolDatasource since we'll be working with an Oracle database. The Oracle datasource class is not present in a legal public Maven repository due to license restrictions.
You can however still use Maven for this dependency as follows;
Download the driver from Oracle;
http://www.oracle.com/technetwork/database/enterprise-edition/jdbc-112010-090769.html
Define a dependency in your pom file as below and make sure the jar file is in the correct place.
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc</artifactId>
<version>14</version>
<scope>system</scope>
<systemPath>${basedir}/lib/ojdbc6.jar</systemPath> <--- must match file name
</dependency>
The above is also useful when you're working with Liquibase (http://www.liquibase.org/) from Maven.
Mocking JNDI
JNDI can be mocked with; org.springframework.jndi.support.SimpleNamingContextBuilder
A Maven dependency for this;
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>3.0.6.RELEASE</version>
</dependency>
Below some sample code to create an Oracle Db testuserDS datasource and make it available via JNDI.
SimpleNamingContextBuilder builder = null;
OracleConnectionPoolDataSource ds = new OracleConnectionPoolDataSource();
ds.setURL("jdbc:oracle:thin:@localhost:1521:xe");
ds.setUser("testuser");
ds.setPassword("testuser");
builder = SimpleNamingContextBuilder.emptyActivatedContextBuilder();
builder.bind("java:comp/env/jdbc/testuserDS", ds); // mocking for default Java application lookups
builder.bind("jdbc/testuserDS", ds); // mocking for hibernate
The above code can be placed in the BeforeClass method if you're using JUnit.
Mocking final classes, statics and private parts
Mockito in combination with Powermock make your life a lot easier to accomplish the tasks as specified in the topic. The class to be tested;
public final class App {
private String getGreeting() {
return "Hello";
}
public static String getName() {
return "Maarten";
}
public String helloName() {
return getGreeting()+ " " + getName();
}
}
My unit test class is shown below. It illustrates mocking of a final class, a static method and a private method in that class.
import static org.junit.Assert.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import static org.powermock.api.mockito.PowerMockito.spy;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
/**
*
* @author maarten
*/
@RunWith(PowerMockRunner.class)
@PrepareForTest(App.class)
@PowerMockIgnore("javax.crypto.*")
public class AppTest {
/**
* Test of helloName method, of class App.
*/
@Test
public void testHelloName() throws Exception {
System.out.println("helloName");
App instance = spy(new App()); //this allows me to mock private methods
PowerMockito.mockStatic(App.class); //this allows me to mock static methods
PowerMockito.when(App.getName()).thenReturn("MockedMaarten"); //mocking static method here
PowerMockito.doReturn("MockedHello").when(instance,"getGreeting"); //mocking private method here
String expResult = "MockedHello MockedMaarten";
String result = instance.helloName();
assertEquals(expResult, result);
}
}
For reference and to provide an easy way to get the dependencies, my pom file is as follows. Using properties to specify dependency versions is recommended.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>ms.utils</groupId>
<artifactId>JavaTestProject</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>JavaTestProject</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<mockito.version>1.9.5</mockito.version>
<powermock.version>1.5.1</powermock.version>
<junit.version>4.10</junit.version>
<springframework.version>3.0.6.RELEASE</springframework.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${springframework.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
No comments:
Post a Comment