Unit Testing with JUnit and EasyMock: Three Practical Examples
Example 1: Pure Java Testing
Requirement
Build a simple currency fund selector supporting CNY (Chinese Yuan), USD (US Dollar), GBP (British Pound), EUR (Euro), and CAD (Canadian Dollar). The selector algorithm chooses the currency with the highest exchange rate for investmant. This example demonstrates JUnit and EasyMock integration with detailed code and test results.
ExchangeRate Interface
import java.io.IOException;
public interface ExchangeRate {
double queryRate(String sourceCurrency, String targetCurrency) throws IOException;
}
Class Under Test: Money.java
import java.io.IOException;
public class Money {
private String currencyCode;
private long wholeUnits;
private int fractionalUnits;
public Money(double value, String currencyCode) {
this.currencyCode = currencyCode;
decomposeAmount(value);
}
private void decomposeAmount(double value) {
this.wholeUnits = (long) value;
this.fractionalUnits = (int) (value * 100) % 100;
}
public Money convert(ExchangeRate rateProvider) {
String[] supportedCurrencies = {"CNY", "USD", "GBP", "EUR", "CAD"};
String targetCode = null;
double highestRate = 0.0;
double currentRate;
try {
for (String code : supportedCurrencies) {
currentRate = rateProvider.queryRate(this.currencyCode, code);
if (currentRate > highestRate) {
targetCode = code;
highestRate = currentRate;
}
}
if (targetCode.equals(this.currencyCode)) {
return this;
}
double originalValue = wholeUnits + fractionalUnits / 100.0;
return new Money(originalValue * highestRate, targetCode);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Money) {
Money other = (Money) obj;
return this.currencyCode.equals(other.currencyCode)
&& this.wholeUnits == other.wholeUnits
&& this.fractionalUnits == other.fractionalUnits;
}
return false;
}
@Override
public String toString() {
return wholeUnits + "." + fractionalUnits + " " + currencyCode;
}
}
Test Implementation
import junit.framework.TestCase;
import org.easymock.EasyMock;
import org.junit.Test;
import java.io.IOException;
public class MoneyTest extends TestCase {
@Test
public void testCurrencyConversion() throws IOException {
Money subject = new Money(2.5, "USD");
Money expectedResult = new Money(3.75, "EUR");
ExchangeRate rateMock = EasyMock.createMock(ExchangeRate.class);
EasyMock.expect(rateMock.queryRate("USD", "USD")).andReturn(1.0);
EasyMock.expect(rateMock.queryRate("USD", "CNY")).andReturn(0.5);
EasyMock.expect(rateMock.queryRate("USD", "GBP")).andReturn(1.1);
EasyMock.expect(rateMock.queryRate("USD", "EUR")).andReturn(1.5);
EasyMock.expect(rateMock.queryRate("USD", "CAD")).andReturn(0.3);
EasyMock.replay(rateMock);
Money actualResult = subject.convert(rateMock);
assertEquals(expectedResult, actualResult);
}
}
Important Notes
The toString() override in the Money class serves the assertEquals comparison—when two objects are compared, their string representations are generated first via toString(), then compared using equals().
The equals() override is essential because the default Object.equals() compares object references by memory address rather than actual content.
Example 2: Simulating JavaEE Environment
Target Class: AuthServlet.java
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class AuthServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String user = request.getParameter("username");
String pass = request.getParameter("password");
if ("admin".equals(user) && "admin123".equals(pass)) {
ServletContext servletContext = getServletContext();
RequestDispatcher rd = servletContext.getNamedDispatcher("mainDispatcher");
rd.forward(request, response);
} else {
throw new RuntimeException("Authentication failed.");
}
}
}
Test Class
import junit.framework.TestCase;
import org.easymock.EasyMock;
import org.junit.Before;
import org.junit.Test;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
public class AuthServletTest extends TestCase {
private HttpServletRequest mockRequest;
private ServletContext mockServletContext;
private RequestDispatcher mockDispatcher;
private AuthServlet servlet;
@Before
public void initialize() {
mockRequest = EasyMock.createMock(HttpServletRequest.class);
mockServletContext = EasyMock.createMock(ServletContext.class);
mockDispatcher = EasyMock.createMock(RequestDispatcher.class);
servlet = new AuthServlet();
}
@Test
public void testSuccessfulAuthentication() throws Exception {
EasyMock.expect(mockRequest.getParameter("username")).andReturn("admin");
EasyMock.expect(mockRequest.getParameter("password")).andReturn("admin123");
EasyMock.expect(mockServletContext.getNamedDispatcher("mainDispatcher")).andReturn(mockDispatcher);
mockDispatcher.forward(mockRequest, null);
EasyMock.replay(mockRequest);
EasyMock.replay(mockServletContext);
EasyMock.replay(mockDispatcher);
AuthServlet testServlet = new AuthServlet() {
@Override
public ServletContext getServletContext() {
return mockServletContext;
}
};
testServlet.doPost(mockRequest, null);
EasyMock.verify(mockRequest);
EasyMock.verify(mockServletContext);
EasyMock.verify(mockDispatcher);
}
@Test
public void testFailedAuthWrongUsername() {
EasyMock.expect(mockRequest.getParameter("username")).andReturn("wronguser");
EasyMock.expect(mockRequest.getParameter("password")).andReturn("admin123");
EasyMock.replay(mockRequest);
try {
servlet.doPost(mockRequest, null);
} catch (Exception e) {
assertEquals("Authentication failed.", e.getMessage());
}
EasyMock.verify(mockRequest);
}
@Test
public void testFailedAuthWrongPassword() {
EasyMock.expect(mockRequest.getParameter("username")).andReturn("admin");
EasyMock.expect(mockRequest.getParameter("password")).andReturn("wrongpass");
EasyMock.replay(mockRequest);
try {
servlet.doPost(mockRequest, null);
} catch (Exception e) {
assertEquals("Authentication failed.", e.getMessage());
}
EasyMock.verify(mockRequest);
}
@Test
public void testFailedAuthNullUsername() {
EasyMock.expect(mockRequest.getParameter("username")).andReturn(null);
EasyMock.expect(mockRequest.getParameter("password")).andReturn("admin123");
EasyMock.replay(mockRequest);
try {
servlet.doPost(mockRequest, null);
} catch (Exception e) {
assertEquals("Authentication failed.", e.getMessage());
}
EasyMock.verify(mockRequest);
}
@Test
public void testFailedAuthNullPassword() {
EasyMock.expect(mockRequest.getParameter("username")).andReturn("admin");
EasyMock.expect(mockRequest.getParameter("password")).andReturn(null);
EasyMock.replay(mockRequest);
try {
servlet.doPost(mockRequest, null);
} catch (Exception e) {
assertEquals("Authentication failed.", e.getMessage());
}
EasyMock.verify(mockRequest);
}
}
Key Insight
The anonymous inner class pattern is necessary because AuthServlet calls getServletContext() internally. To properly test this servlet in isolation, we overide this method in a subclass to return our mocked context, allowing complete control over servlet container dependencies.
Example 3: Database Access Layer Testing
Database Utility Interface
package com.example.testing;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
public interface DatabaseHelper {
Connection establishConnection();
void releaseConnection(Connection conn);
void releaseStatement(Statement stmt);
void releaseResultSet(ResultSet rs);
}
Sales Order Entity
package com.example.testing;
import java.sql.ResultSet;
import java.sql.SQLException;
public interface PurchaseOrder {
String getOrderId();
String getTerritory();
double getOrderTotal();
void setOrderId(String id);
void setTerritory(String territory);
void setOrderTotal(double total);
void populateFromResultSet(ResultSet results) throws SQLException;
String calculateDiscountTier();
}
PurchaseOrderImpl.java
package com.example.testing;
import java.sql.ResultSet;
import java.sql.SQLException;
public class PurchaseOrderImpl implements PurchaseOrder {
private String orderId;
private String territory;
private double orderTotal;
public String getOrderId() { return orderId; }
public String getTerritory() { return territory; }
public double getOrderTotal() { return orderTotal; }
public void setOrderId(String id) { this.orderId = id; }
public void setTerritory(String t) { this.territory = t; }
public void setOrderTotal(double total) { this.orderTotal = total; }
@Override
public void populateFromResultSet(ResultSet results) throws SQLException {
orderId = results.getString(1);
territory = results.getString(2);
orderTotal = results.getDouble(3);
}
@Override
public String calculateDiscountTier() {
double points = orderTotal;
if ("Africa".equalsIgnoreCase(territory)) {
points = orderTotal;
} else if ("Asia Pacific".equalsIgnoreCase(territory)) {
points = orderTotal * 0.9;
} else if ("Europe".equalsIgnoreCase(territory)) {
points = orderTotal * 0.85;
} else if ("America".equalsIgnoreCase(territory)) {
points = orderTotal * 0.8;
} else {
points = orderTotal * 0.75;
}
if (points < 500) return "Tier_1";
if (points < 1000) return "Tier_2";
if (points < 2000) return "Tier_3";
if (points < 4000) return "Tier_4";
return "Tier_5";
}
}
Test Suite
package com.example.testing;
import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.demo.matcher.SqlMatcher.sqlEquals;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import junit.framework.TestCase;
import org.easymock.EasyMock;
import org.easymock.IMocksControl;
public class PurchaseOrderTest extends TestCase {
public void testDiscountCalculation() {
IMocksControl mockControl = EasyMock.createControl();
DatabaseHelper mockDbHelper = mockControl.createMock(DatabaseHelper.class);
Connection mockConn = mockControl.createMock(Connection.class);
Statement mockStmt = mockControl.createMock(Statement.class);
ResultSet mockRs = mockControl.createMock(ResultSet.class);
try {
mockDbHelper.establishConnection();
EasyMock.expectLastCall().andStubReturn(mockConn);
mockConn.createStatement();
expectLastCall().andStubReturn(mockStmt);
mockStmt.executeQuery(sqlEquals("SELECT * FROM purchase_orders"));
expectLastCall().andStubReturn(mockRs);
mockRs.next();
expectLastCall().andReturn(true).times(3);
expectLastCall().andReturn(false).times(1);
mockRs.getString(1);
expectLastCall().andReturn("ORD-001").times(1);
expectLastCall().andReturn("ORD-002").times(1);
expectLastCall().andReturn("ORD-003").times(1);
mockRs.getString(2);
expectLastCall().andReturn("Asia Pacific").times(1);
expectLastCall().andReturn("Europe").times(1);
expectLastCall().andReturn("America").times(1);
mockRs.getDouble(3);
expectLastCall().andReturn(350.0).times(1);
expectLastCall().andReturn(1350.0).times(1);
expectLastCall().andReturn(5350.0).times(1);
mockControl.replay();
Connection conn = mockDbHelper.establishConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("select * from purchase_orders");
String[] expectedTiers = {"Tier_1", "Tier_3", "Tier_5"};
int index = 0;
while (rs.next()) {
PurchaseOrder order = new PurchaseOrderImpl();
order.populateFromResultSet(rs);
assertEquals(expectedTiers[index], order.calculateDiscountTier());
index++;
}
mockControl.verify();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Custom Argument Matcher
package com.example.testing;
import static org.easymock.EasyMock.reportMatcher;
import org.easymock.IArgumentMatcher;
public class SqlMatcher implements IArgumentMatcher {
private final String expectedSql;
public SqlMatcher(String expected) {
this.expectedSql = expected;
}
@Override
public void appendTo(StringBuffer buffer) {
buffer.append("SqlMatcher(\"" + expectedSql + "\")");
}
@Override
public boolean matches(Object actual) {
if (actual == null && expectedSql == null) return true;
if (actual instanceof String) {
return expectedSql.equalsIgnoreCase((String) actual);
}
return false;
}
public static String sqlEquals(String input) {
reportMatcher(new SqlMatcher(input));
return input;
}
}