JUnit Tutorial for Beginners
1. Introduction
In this post, we will discus the basics of setting up your JUnit Test cases. We’ll go step by step in creating test cases as we go along with creating our application. Before we dive into it though, why do we even need to create test cases? Isn’t it enough to just create the implementation since it’s what we are delivering anyway?
Although the actual implementation is part of the package, the JUnit Test case is a bullet proof evidence that what we wrote is what the actual requirements or functions will do. It is the concrete basis of a specific unit/function which does what it needs to do.
Knowing the impact in the stability of the application. The JUnit Test cases defines the stability of an application even after several extensions to it. If done correctly, it guarantees that the extension made to the system will not break the entire system as a whole. How does it prevent it? If the developers write clean unit and integration tests, it will report any side effects via the reporting plugins that the application uses.
Regression and Integration Testing. The effort of testing is relative to the applications size and changes done. By creating JUnit Test cases, regression and integration tests can be automated and can definitely save time and effort.
Overall, creating JUnit Test cases are definitely a must do by all developers, sadly there are still who don’t uses it’s power to it’s full extent and some just doesn’t do it. It’s sometimes a shame to think that one of the purest way of developing bullet proof code is not done by the developers. It can be because of the lack of training, experience or just pressure of not delivering the actual value (which is a problem within itself since although not part of the implementation, it’s a most valuable component of your code) but thats not an excuse especially that software are now globally taking over most of the major systems (medical, auto, planes, buildings) in the world. The stability of these systems rely on the stability of the unit test cases.
So as a precursor to being a skilled full blown developer that loves to do unit test, let’s dive into some of the beginners guide into doing it.
2. Tools
For this example, I’ll be using Java as the platform, Eclipse as the IDE, and Maven as the project management tool. If you’re not yet familiar with these tools, please visit the Java, Eclipse IDE and Maven site.
3. Step by Step Guide
3.1 Create your project
Let’s create a project first.
After creating the project, you’ll be shown a project like the one below:
Make sure you include the Junit Library to your dependency list.
pom.xml
<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>com.jgc.areyes.junit</groupId> <artifactId>junit-test-beginners-example</artifactId> <version>0.0.1-SNAPSHOT</version> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies> </project>
3.2 Create the Service Class
Majority of the developers I know starts first with creating the implementation rather than the JUnit Test case, it isn’t a bad practice at all but wouldn’t it be more concrete if we create the JUnit Test cases first based on the design then create the implementation to pass all JUnit Test case? This is the case in the TDD, one of the most successful schemes of actual software development.
Assuming we are creating a service to manage an account. We need to introduce the following service methods:
- Create a new Account
- Update an Account
- Remove an Account
- List All Account Transactions
We have an OOP design that will handle this service and so we introduce the following classes (Class Diagram).
Here is the actual class that doesn’t have any implementation yet. We will create the implementation after creating the test cases for this class.
AccountServiceImpl.java
package com.areyes1.jgc.svc; import java.util.List; import com.areyes1.jgc.intf.AccountService; import com.areyes1.jgc.obj.Account; import com.areyes1.jgc.obj.Transaction; public class AccountServiceImpl implements AccountService { public Account createNewAccount(Account account) { // TODO Auto-generated method stub return null; } public Account updateAccount(Account account) { // TODO Auto-generated method stub return null; } public Account removeAccount(Account account) { // TODO Auto-generated method stub return null; } public List listAllTransactions(Account account) { // TODO Auto-generated method stub return null; } }
3.3 Create JUnit Test Cases
Now that we have the service placeholder, let’s create the Junit Test case for the AccountServiceImpl class. The Test cases will be the basis of your class functional aspect so you as a developer should write a solid and good test case (and not just fake it to pass).
When you create a test case, it will initially look like this:
AccountServiceImplTest.java
/** * */ package com.areyes1.jgc.svc; import static org.junit.Assert.*; import org.junit.After; import org.junit.Before; import org.junit.Test; /** * @author alvinreyes * */ public class AccountServiceImplTest { /** * Test method for {@link com.areyes1.jgc.svc.AccountServiceImpl#createNewAccount(com.areyes1.jgc.obj.Account)}. */ @Test public void testCreateNewAccount() { fail("Not yet implemented"); } /** * Test method for {@link com.areyes1.jgc.svc.AccountServiceImpl#updateAccount(com.areyes1.jgc.obj.Account)}. */ @Test public void testUpdateAccount() { fail("Not yet implemented"); } /** * Test method for {@link com.areyes1.jgc.svc.AccountServiceImpl#removeAccount(com.areyes1.jgc.obj.Account)}. */ @Test public void testRemoveAccount() { fail("Not yet implemented"); } /** * Test method for {@link com.areyes1.jgc.svc.AccountServiceImpl#listAllTransactions(com.areyes1.jgc.obj.Account)}. */ @Test public void testListAllTransactions() { fail("Not yet implemented"); } }
As it can be seen above, it will fail the test case if it’s not yet implemented. From here on now, it’s a matter of discipline from the developer to create concrete and solid test cases.
Observing the JUnit Test cases.
- Using @Test to define a test method
- Put them all on the Test package (src/test/main/)
- Class always has a suffix of Test (AccountServiceImplTest)
- Methods always starts with “test”.
Here’s is the modified version of the JUnit Test cases. This will now be the basis of our implementation code.
AccountServiceImplTest.java
/** * */ package com.areyes1.jgc.svc; import static org.junit.Assert.*; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.hamcrest.CoreMatchers.*; import com.areyes1.jgc.obj.Account; /** * @author alvinreyes * */ public class AccountServiceImplTest { AccountServiceImpl accountService = new AccountServiceImpl(); /** * Test method for {@link com.areyes1.jgc.svc.AccountServiceImpl#createNewAccount(com.areyes1.jgc.obj.Account)}. */ @Test public void testCreateNewAccount() { Account newAccount = new Account(); newAccount.setName("Alvin Reyes"); newAccount.setDescription("This is the description"); Account newAcccountInserted = accountService.createNewAccount(newAccount); // Check if the account has the same composition. assertThat(newAccount, isA(Account.class)); assertEquals(newAccount.getName(), newAcccountInserted.getName()); } /** * Test method for {@link com.areyes1.jgc.svc.AccountServiceImpl#updateAccount(com.areyes1.jgc.obj.Account)}. */ @Test public void testUpdateAccount() { // The old account (assumed that this came from a database or mock) Account oldAccount = new Account(); oldAccount.setName("Alvin Reyes"); oldAccount.setDescription("This is the description"); String name = oldAccount.getName(); // Check if the account is still the same. it is expected to be different since we updated it. Account expectedAccountObj = new Account(); expectedAccountObj = accountService.updateAccount(oldAccount); assertThat(expectedAccountObj, isA(Account.class)); assertNotEquals(name, expectedAccountObj.getName()); } /** * Test method for {@link com.areyes1.jgc.svc.AccountServiceImpl#removeAccount(com.areyes1.jgc.obj.Account)}. */ @Test public void testRemoveAccount() { // Set up the account to be removed. Account toBeRemovedAccount = new Account(); toBeRemovedAccount.setName("Alvin Reyes"); toBeRemovedAccount.setDescription("This is the description"); // Removed the account. assertTrue(accountService.removeAccount(toBeRemovedAccount)); } /** * Test method for {@link com.areyes1.jgc.svc.AccountServiceImpl#listAllTransactions(com.areyes1.jgc.obj.Account)}. */ @Test public void testListAllTransactions() { // Dummy Transactions (can be mocked via mockito) Account account = new Account(); account.setName("Alvin Reyes"); // Service gets all transaction accountService.listAllTransactions(account); // Check if there are transactions. assertTrue(accountService.listAllTransactions(account).size() > 1); } }
Running the test case will show the following result.
It failed cause we still have to code our implementation. Now in the implementation of our logic, our goal is make sure that these test cases succeeds!
3.4 Code the implementation
Now that the test cases are setup, we can now code our implementation. The test cases we created above will be the basis of how we will create the implementation. The goal is to pass the test cases!
package com.areyes1.jgc.svc; import java.util.ArrayList; import java.util.List; import com.areyes1.jgc.intf.AccountService; import com.areyes1.jgc.obj.Account; import com.areyes1.jgc.obj.Transaction; public class AccountServiceImpl implements AccountService { public Account createNewAccount(Account account) { // Dummy Dao! Database insert here. // accountDao.insert(account); // Ultimately return the account with the modification. return account; } public Account updateAccount(Account account) { // Dummy Dao! Database insert here. // accountDao.update(account); // Ultimately return the account with the modification. account.setName("Alvin Reyes: New Name"); return account; } public boolean removeAccount(Account account) { // Dummy Dao! Database insert here. // accountDao.delete(account); // Ultimately return the account with the modification. // if exception occurs, return false. return true; } public List listAllTransactions(Account account) { // accountDao.loadAllTransactions(account); List listOfAllTransactions = new ArrayList(); listOfAllTransactions.add(new Transaction()); listOfAllTransactions.add(new Transaction()); listOfAllTransactions.add(new Transaction()); account.setTransactions(listOfAllTransactions); return listOfAllTransactions; } }
Running the test case will show the following result.
3.5 Run your maven
Run your maven to see results.
------------------------------------------------------- T E S T S ------------------------------------------------------- Running com.areyes1.jgc.svc.AccountServiceImplTest Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.051 sec Results : Tests run: 4, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ junit-test-beginners-example --- [INFO] Building jar: /Users/alvinreyes/EclipseProjects/Java/junit-test-beginners-example/target/junit-test-beginners-example-0.0.1-SNAPSHOT.jar [INFO] [INFO] --- maven-install-plugin:2.4:install (default-install) @ junit-test-beginners-example --- [INFO] Installing /Users/alvinreyes/EclipseProjects/Java/junit-test-beginners-example/target/junit-test-beginners-example-0.0.1-SNAPSHOT.jar to /Users/alvinreyes/.m2/repository/com/jgc/areyes/junit/junit-test-beginners-example/0.0.1-SNAPSHOT/junit-test-beginners-example-0.0.1-SNAPSHOT.jar [INFO] Installing /Users/alvinreyes/EclipseProjects/Java/junit-test-beginners-example/pom.xml to /Users/alvinreyes/.m2/repository/com/jgc/areyes/junit/junit-test-beginners-example/0.0.1-SNAPSHOT/junit-test-beginners-example-0.0.1-SNAPSHOT.pom [INFO] [INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ junit-test-beginners-example --- [WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent! [INFO] Copying 0 resource [INFO] [INFO] --- maven-compiler-plugin:2.5.1:compile (default-compile) @ junit-test-beginners-example --- [INFO] Nothing to compile - all classes are up to date [INFO] [INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ junit-test-beginners-example --- [WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent! [INFO] Copying 0 resource [INFO] [INFO] --- maven-compiler-plugin:2.5.1:testCompile (default-testCompile) @ junit-test-beginners-example --- [INFO] Nothing to compile - all classes are up to date [INFO] [INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ junit-test-beginners-example --- [INFO] Skipping execution of surefire because it has already been run for this configuration [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 9.456 s [INFO] Finished at: 2015-10-12T16:28:01-05:00 [INFO] Final Memory: 11M/28M [INFO] ------------------------------------------------------------------------
4. Download the Eclipse project
This was an example of JUnit Test Beginners Tutorial
You can download the full source code of this example here: junit-test-beginners-example