Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Unit Testing with JUnit and EasyMock: Three Practical Examples

Tech May 7 3

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;
    }
}

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.