Unit Testing Java Code with JUnit
Explains how to use JUnit to do unit testing in Java.
Prerequisites
- Understand Java classes, objects, and methods
- Understand Java variable scope (local, instance, and class) and method scope (instance and class)
Objectives
- Understand definition and purpose of unit testing
- Understand purpose of JUnit library
- Use JUnit Assert functions in a unit test
Definitions
- Unit
- A part of a program, ranging from as small as a single method to as large as all the classes in a package, often a single class
- Unit test
- Code to test a unit of a program
- JUnit
- An open source library of Java code that aids unit testing of Java programs; available from junit.org. JUnit’s purpose is to automate the verfication of unit test results.
How to Use JUnit
In order to use JUnit to test one or more Java methods, do the following.
- Write one or more Java methods in a class.
- In a separate Java file import your Java class and the JUnit classes.
- Within the same file as the previous step, write a class that contains test methods. Each test method must be annotated with
@org.junit.Test
. - Within each test method, write code to call your Java methods, then write one or more calls to
org.junit.Assert.assertEquals
or the other assert methods. - Run the test class that you wrote.
- When you run your test class,
assertEquals
will compare the value returned from your Java method to the value that you expect your Java method will return.
The assertEquals
method accepts two or three parameters,
namely:
assertEquals(expected, actual, delta);
- Compares the expected value to the actual value.
expected
: the value that you expect your Java function to return.
actual
: the value that your Java function returned.
delta
: only needed for floats or doubles; the maximum difference between expected and actual that you're willing to accept and still consider the numbers to be equal.
There are several other assert methods in the
org.junit.Assert
class, including assertFalse
,
assertTrue
, assertNull
,
assertSame
, and assertArrayEquals
. See the
online java docs
for junit.
Example
The following four Java classes contain simple methods that simulate
a bank that offers savings accounts and checking accounts to its
customers. If you’re learning JUnit for the first time, the details of
the four Java classes may not be important right now. You may want to
skip down to examine the TestBank
class which uses JUnit.
import java.util.HashMap; public class Bank { // Use the singleton design pattern to ensure that // the computer creates only one Bank object each // time it runs this program. private static final Bank singleton = new Bank(); public static Bank getInstance() { return singleton; } public static int getNextAccountId() { int id; synchronized(singleton) { id = singleton.nextAccountId; singleton.nextAccountId = id + 1; } return id; } private final HashMap<Integer, Account> accounts; private int nextAccountId; private Bank() { this.accounts = new HashMap<Integer, Account>(); this.nextAccountId = 1; } public void addAccount(Account account) { this.accounts.put(account.getAccountId(), account); } public Account getAccount(int accountId) { return this.accounts.get(accountId); } }
import java.util.Date; public abstract class Account { private final int accountId; private final Date dateOpened; protected double balance; public Account() { this.accountId = Bank.getNextAccountId(); this.dateOpened = new Date(); this.balance = 0; } public int getAccountId() { return this.accountId; } public double getBalance() { return this.balance; } public Date getDateOpened() { return this.dateOpened; } public abstract void deposit(double amount); public abstract void withdraw(double amount); @Override public int hashCode() { return this.accountId; } }
public class SavingsAccount extends Account { public static final double MINIMUM_BALANCE = 25.0; public void deposit(double amount) { if (amount <= 0) { throw new IllegalArgumentException( "Incorrect amount; deposit amount is " + amount + " but must be greater than 0."); } this.balance += amount; } public void withdraw(double amount) { if (amount <= 0) { throw new IllegalArgumentException( "Incorrect amount; withdrawal amount is " + amount + " but must be greater than 0."); } double newBalance = this.balance - amount; if (newBalance < MINIMUM_BALANCE) { throw new IllegalArgumentException( "Insufficient funds to withdraw " + amount + ". Current balance is " + this.balance + ". Balance after withdrawal would be " + + newBalance + " which is less than the" + " minimum balance of " + MINIMUM_BALANCE); } this.balance = newBalance; } }
public class CheckingAccount extends Account { public static final double OVERDRAFT_LIMIT = 500; public static final double OVERDRAFT_FEE = 25; public void deposit(double amount) { if (amount <= 0) { throw new IllegalArgumentException( "Incorrect amount; deposit amount is " + amount + " but must be greater than 0."); } this.balance += amount; } public void withdraw(double amount) { if (amount <= 0) { throw new IllegalArgumentException( "Incorrect amount; withdrawal amount is " + amount + " but must be greater than 0."); } double newBalance = this.balance - amount; if (newBalance < 0) { throw new IllegalArgumentException( "Insufficient funds to withdraw " + amount + ". Current balance is " + this.balance + ". Balance after withdrawal would be " + newBalance + "."); } this.balance = newBalance; } public void settleCheck(double amount) { if (amount <= 0) { throw new IllegalArgumentException( "Incorrect amount; check amount is " + amount + " but must be greater than 0."); } double newBalance = this.balance - amount; if (newBalance < 0) { newBalance -= OVERDRAFT_FEE; if (newBalance < -OVERDRAFT_LIMIT) { throw new IllegalArgumentException( "Insufficient funds to pay a check for " + amount + ". Current balance is " + this.balance + ". Balance after paying" + " the check and overdraft fee would be " + newBalance + " which is less than the" + " overdraft limit."); } } this.balance = newBalance; } }
The TestBank
class below uses JUnit to test the
previous four Java classes. Notice that the test methods call the
assertEquals
method and other assert methods many times.
Each time it is called, the assertEquals
method will
compare an expected value to an actual value. This is how test methods
automatically verify that a method works correctly.
import org.junit.Test; import static org.junit.Assert.*; import java.util.Date; public class TestBank { private static final double DELTA = 0.001; // The @Test annotation must appear // before each unit test method. @Test public void testBank() { Bank bank = Bank.getInstance(); // Add two accounts to the bank. Account sa = new SavingsAccount(); Account ca = new CheckingAccount(); bank.addAccount(sa); bank.addAccount(ca); // Find the first the account that was just added. Account f = bank.getAccount(sa.getAccountId()); // Verify that the getAccount method returned the // correct account. assertSame(f, sa); // Find the second the account that was just added. f = bank.getAccount(ca.getAccountId()); assertSame(f, ca); // Attempt to find an account that doesn't exist. f = bank.getAccount(-1); assertNull(f); } @Test public void testSavingsAccount() { SavingsAccount acc = new SavingsAccount(); // Verify that SavingsAccount extends Account. assertTrue(acc instanceof SavingsAccount); assertTrue(acc instanceof Account); // Verify that the account's date opened is correct. Date now = new Date(); Date opened = acc.getDateOpened(); assertEquals(now.getTime(), opened.getTime(), 1000); // Verify that the new account's balance is 0. assertEquals(0, acc.getBalance(), 0); // Deposit $50 and ensure the balance is $50. acc.deposit(50); assertEquals(50, acc.getBalance(), DELTA); // Withdraw $25 and ensure the balance is $25. acc.withdraw(25); assertEquals(25, acc.getBalance(), DELTA); // Attempt to withdraw more money than is in the // account and ensure the withdraw method throws // an exception and doesn't change the balance. IllegalArgumentException thrown = null; try { acc.withdraw(40); } catch (IllegalArgumentException ex) { thrown = ex; } assertNotNull(thrown); assertEquals(25, acc.getBalance(), DELTA); } }