Overview of This Lesson

Refresh this page because I am probably still making changes to it.

JUnit tests are an important part of program development: Nothing is worse than presenting your application to the client and having it crash, or receiving the angry phone call from a user complaining about a program that doesn't work as it should. In this lesson we'll use your prior experience with JUnit testing to test your Spring applications.

Helpful Resources

API links in this lesson

Overview of JUnit Testing with Java EE

Why do we test our code?

Imagine you are demonstrating your program to the client to show them how the project is coming, and the program crashes due to a bug or coding error! Your client is not going to be impressed. Imagine you developed an application for a client and a bug or mistake caused them to lose thousands (or hundreds of thousands) of dollars. Imagine you developed a part of the software for autonomous vehicles, a piece of vital medical equipment, or an air control system. Mistakes in those applications could cost lives.

Why do we unit test, why do we use tools like JUnit? Why don't we just look at our code and desk-check it (go over it manually and write down what the values of variables are, what is output, etc)? Why don't we just add a bunch of System.out.println() statements to make sure our code is doing what it should be doing?

The process of using printlns is actually referred to as LMAO (Looking Manually At Output), or even more specifically, LMFAO (Looking Manually For Abnormal Output). The problem with this technique is that:

For example, say you decide to modify the player/teams program to include the ability to add, edit, and remove teams. You have to make sure that all the parts of the program that work with a team still function: adding/editing/deleting a player, creating the drop down list of teams, retrieving the list of teams, etc. You'll have to add printlns to track all the variables, data members, method outputs, sql query results, parameters, and anything else having anything to do with the teams.

In a large application for a professional league like the NHL or World Cup Football, where you're also keeping track of games and matches, scores and game details, this gets to be a tedious process that in some cases might take so long that it becomes impossible. Additionally, inspecting this kind of data visually is not guaranteed to find mistakes: humans are prone to error and after just a few solid minutes of examining output, it's very easy to miss something.

Tools like JUnit can make this process easier and automated. You don't have to look over hundreds of lines of output: a simple set of JUnit tests can tell you right away if actual results don't match expected results.

Unit tests are run every time the project is rebuilt, so it's automatic. Where visually inspecting output might tell you of any current mistakes, unit testing can flag mistakes in future builds without you having to do a lot of extra work. In fact, many programming teachers use unit tests to test your code in your assignments! It's much faster than manually testing the program with different data sets!

JUnit Setup

JUnit is already part of the Spring Web dependency. You can view it in your POM file. You'll see it as "spring-boot-starter-test". In fact, if you look at the XML code for your POM file, you'll notice it looks like this:


The <scope>test</scope> element means that this dependency will be ignored when this application is packaged and deployed. Anything with test-level scope is treated this way, and that's a good thing: you don't want those things taking up space/resources in your application when they're not something the client would need or use.

If you're using an older version of Java (15 or earlier) you might have problems with JUnit5 and will need to set up your projects to use JUnit4. You only need to follow the instructions below if you can't run JUnit5.

  1. Right-click the project in which you want to use JUnit and select Build Path > Configure Build Path.
  2. In the Libraries tab, select "Classpath". Then click "Add Library".
    in libraries select classpath then click add library in the list of buttons on the right
    Add a library to the Classpath
  3. In the "Add Library" dialog, select "JUnit" and then click NEXT.
    in the list, find JUnit and then click NEXT
    Select JUnit and click NEXT
  4. In the next dialog, select JUnit4 and then click FINISH.
    in the junit library version drop down list, select junit 4
    Select JUnit4 and click FINISH
  5. In the build path, ensure that JUnit4 is now listed under Classpath. Don't click Apply and Close yet.
    JUnit4 should be listed under the classpath node
    Verify JUnit 4 is under Classpath

Now, make note of the Java SE version that's listed under the Modulepath in the same window where you just added JUnit4 to the classpath. In the first image above, you can see above my classpath node is the modulepath node, and it lists Java SE-11.

  1. In the list along the left side, select "Java Compiler". If prompted to apply changes to the build path, go ahead and say Yes/OK.
  2. Near the top of the dialog for Java Compiler you'll see "JDK Compliance" and under that, a checkbox for "Use compliance from execution environment....".
  3. Look at the drop down list on the right: it should have the same version number as the one you just made note of under the modulepath setting. If not, change it to the correct number. If the list is disabled, uncheck the checkbox to enable the drop-down list.
  4. Click APPLY AND CLOSE, even if you didn't need to make any changes to this dialog box.

If you didn't have to make any changes to the Java Compiler settings, you likely never will until you install a new Java version, so with future projects, you can skip those steps.

Now you have to edit the default JUnit Run Configuration for the current project and make sure it's set to JUnit4.

  1. Right click your project and select Run As > Run Configurations.
    In the Junit item, select your current project, then change test runner to junit 4 from the drop down list
    Change Test Runner to JUnit4
  2. NOTE: if you don't see an item for your current project in the JUnit category, make sure the "JUnit" node is selected and then click the tiny "New Launch Configuration" button right above the "type filter text" field.
  3. Find the JUnit node in the list on the left and open it. Then look for your current project and select it.
  4. In the middle of the dialog on the "Test" tab, look for "Test Runner:" and select "JUnit 4" if it's not already set.
  5. Click APPLY, then click CLOSE

Database Tests

Let's get right to it and try it out. Open up your project for Exercise 3 from the lesson on Sessions. If you chose not to complete that exercise, use any project where you've done some database access.

Database Access Tests

We'll start with some database access tests. In the Project Explorer, go to your project node's src/test/java directory and expand it. This is where all of your JUnit test classes go!

Right-click the package in there and select New > Other and then search for and select JUnit Test Case. Then click the NEXT button.

Above the class header, add the following annotations:

Adding these annotations will allow us to create test methods inside this class that will be able to run on a "test version" of our application, including the database we've created.

So we're ready to add some test methods. To test our database access methods, we need a DatabaseAccess instance! We can @Autowired one in:

private DatabaseAccess da;

Now let's add a method to test our inventory insert method:

public void testInsertInventory_getInventory() {
    // setup an inventory object to insert
    Inventory inv = new Inventory(123, "Manos del Uruguay Fino Storm Glass", 
            10, 19.99, 2);
    // try the insert of this inventory object
    // check to see if it worked: should be at least 1 item in returned map
    Assert.assertTrue(da.getInventory().size() > 0);
public void testInsertInventory_getInventory() {
    // setup an inventory object to insert
    Inventory inv = new Inventory(123, "Manos del Uruguay Fino Storm Glass", 10, 19.99, 2);
    // try the insert of this inventory object

    // check to see if it worked: should be at least 1 item in returned map
    Assertions.assertTrue(da.getInventory().size() > 0);

This example constructs a new Inventory object and then attempts to insert it using our database access method insertInventory(). We're also testing our getInventory() method too, since we're using it in our assertion: after performing an insert, the size of the inventory list we retrieve must be greater than 0, otherwise the test fails.

Give it a try: Run your tests by right-clicking the project and selecting Run As > JUnit Test. It will take a bit longer than usual, but when it's done, check the JUnit tab near the bottom part of the Eclipse window and you should see if the tests succeeded or failed.

the output in the junit tab shows that the test passed
Running a test that passes

If any of the tests failed, it will be obvious because they'll be marked. Check the test results to find out why they failed and fix the problems.

the output in the junit tab shows that the test failed
Running a test that fails

If you've already got some inventory records in your inventory table, this test doesn't really tell us much at all. Perhaps a better test might be to check how many records we have, attempt the insert, and then make sure that the new number of records is one more than it was before:

public void testInsertInventory_getInventory() {
    // setup an inventory object to insert
    Inventory inv = new Inventory(123, "Manos del Uruguay Fino Storm Glass", 
            10, 19.99, 2);
    // get # of records
    int origNumRecs = da.getInventory().size();
    // try the insert of this inventory object
    // get the new # of records
    int newNumRecs = da.getInventory().size();

    // JUnit4: ------------
    Assert.assertTrue(origNumRecs + 1 == newNumRecs);
    // or this works too:
    //Assert.assertEquals(origNumRecs + 1, newNumRecs);
    // --------------------

    // JUnit5: ------------
    Assertions.assertTrue(origNumRecs + 1 == newNumRecs);
    // or this works too:
    //Assertions.assertEquals(origNumRecs + 1, newNumRecs);
    // --------------------

In this code, we find out how many records there are, do the insert, and then get the number of records again. Then we perform an assertion that tests to see if the original number of records plus 1 is equal to the new number of records.

You could test a delete in the same way: just get the number of records before and after a delete query, and make sure that the original number of records is one less than the new number of records.

Another way to test insert, delete, and update queries is to test the number of rows affected. For example, I created an update method in my application that returned the number of rows affected after an update query:

public int updateInventory(Inventory i) {
String sql = "UPDATE inventory SET itemName=:name, "
    + "quantity=:qty, price=:price, category=:cat"
    + " WHERE itemId=:id;";
.. etc etc...
return jdbc.update(sql, params);  // update() returns # of rows affected

I could now write a JUnit test to test the update method:

public void testUpdateInventory() {

    Inventory origItem = new Inventory(77633, "Everyday Scarves", 2, 14.95, 6);
    // simulate editing the object

    // JUnit4:
    Assert.assertEquals(da.updateInventory(origItem), 1);

    // JUnit5:
    Assertions.assertEquals(da.updateInventory(origItem), 1);

In this test I'm first creating an inventory object for one of the actual inventory items I've already added to my inventory table via the data.sql file. If you prefer, you can always do an insert of a valid item and then retrieve that item and edit it, but that would then be testing 3 different methods, and it's better to ensure your unit tests are testing one specific thing where possible. Regardless, you must test the update on an inventory item that actually exists, otherwise your update will fail.

After creating the inventory object, I write some code to edit the values, then I attempt to perform the update and perform an assertion that checks to make sure the update method returns a 1, for "1 row(s) affected".

Good, Bad, and Boundary Tests

Recall that you must always perform a good test, a bad test, and a boundary test. There isn't much we need to worry about for boundary tests with our database methods, but we can definitely add some bad tests to our good tests.

First, I'm going to change the methods names to reflect that we're doing a good insert test and a bad insert test, and a good update test and a bad update test:

public void testInsertInventory_getInventoryPass() {

public void testUpdateInventoryPass() {


Add a test method testUpdateInventoryFail().

testUpdateInventoryFail() should perform an update that should fail (e.g. update a record with an ID that doesn't exist in the inventory table or change a field value to something invalid). The method itself should pass, but your test should be checking to make sure that the update failed.

Testing for Exceptions

What if we want to test code that might throw an exception? For example, testing the insertInventory() with a duplicate primary key will throw a org.springframework.dao.DuplicateKeyException. We can write a @Test method to test the failure of the insertInventory() method to make sure it throws a DuplicateKeyException when it's supposed to.

If you're using JUnit4, you do this by adding the expected parameter to the @Test attribute:

@Test(expected = DuplicateKeyException.class)
public void testInsertInventory_getInventoryFail() {
    // setup an inventory object to insert with a duplicate primary key
    Inventory inv = new Inventory(77633, "Manos del Uruguay Fino Storm Glass", 
             10, 19.99, 2);
    // try the insert of this inventory object (will fail)

If you're using JUnit5, you can use the assertThrows() method. This method accepts the type of exception you're expecting and a lambda (like a callback) of the method you want to test:

public void testInsertInventory_getInventoryFail() {
    // setup an inventory object to insert with a duplicate primary key
    Inventory inv = new Inventory(77633, "Manos del Uruguay Fino Storm Glass", 
	        10, 19.99, 2);
    // make sure method throws DuplicateKeyException when we insert 
	        () -> da.insertInventory(inv));

This test will attempt to perform the insert on an inventory item with a duplicate key, and that will result in the DuplicateKeyException. The test method is watching for this and will let you know that it occurred.

assertThrows() returns an Exception object so if you want, you can also do an assertion to test that the exception's getMessagse() contains a certain message.

Exception ex =  assertThrows(DuplicateKeyException.class, 
        () -> da.insertInventory(inv));
    assertTrue(ex.getMessage().contains("primary key violation"));

Controller Tests

You should also test controller methods. When testing controller methods, we're really testing to see if they execute as we expect them to when HTTP requests are made to certain URLs. Usually we want to make sure that they load the right resource, but sometimes we also might want to make sure they also get certain results from other methods and/or have certain things in the model.

But how can we write code to create an HTTP request to test out a controller method? We actually use a "mock MVC" environment with "mock requests". We just create a fake or pretend MVC environment and tell it to send fake or pretend requests to our handler methods! Try it!

Add a new class to your src/main/test directory called TestMainController (or Test, followed by the name of your main controller class) and make sure it's in the .controllers package.

For this part of the demonstration, I'm going to test the very basic handler method that loaded the index page from the very first inventory demonstration in the lesson on Sessions (it loaded the inventory index page with a blank Inventory object that we bound to the form):

public String index(Model model, HttpSession session) {
    model.addAttribute("inv", new Inventory());
    return "index.html";

If you're using JUnit4, add the annotation for @RunWith(SpringRunner.class). If you're using JUnit4 or JUnit5, add the annotation @SpringBootTest. Add the @AutoConfigureTestDatabase annotation, also: even though we're not using the database class directly, the controller autowires the same database access class we're autowiring in the other test class. If we don't include this annotation, the program will try to set up the database twice and you'll get errors when it tries to create tables that already exist.

Additionally, you will also need to add the annotation @AutoConfigureMockMvc.

@AutoConfigureMockMvc creates a fake MVC server environment for us to test in. This allows our HTTP requests to be called without the cost of starting (or re-starting) the server every time. It runs our tests on a full Spring application context without wasting server resources. This means that it's only testing the layer below the server where Spring handles any incoming HTTP requests: it hands these requests off to the controller so we can test our controller methods cheaply. So basically, it allows us to create "fake requests" from a "fake view" to our controller so that we can test the controller methods.

Adding the @AutoConfigureMockMvc also automatically injects an instance of the MockMvc class so that we can use it to perform test HTTP requests as if they were real requests. This means we'll want to @Autowired a MockMvc inside our test class:

@RunWith(SpringRunner.class) // only if you have JUnit4
public class TestMainController {

    private MockMvc mockMvc;


Now let's add a method to test the loading of the inventory index page:

public void testLoadingIndexPass() throws Exception {

Note that these methods that test controller handlers will all include the throws Exception clause in the method header. Some of the test methods you'll be calling could throw unchecked exceptions, so we must include this clause, since there's no point in using a try-catch.

This code is going to prompt several static imports, and that's fine:

The testLoadingIndexPass() method uses the MockMvc class's perform() method to perform a GET request (you can probably guess that if we were doing a POST request, you would use mockMvc.perform(post(...)) instead).

The perform() method accepts a RequestBuilder object, which models a specific (fake) HTTP request. In this case, get() returns an HTTP GET request. If you used post(), that would return an HTTP POST request. These methods create "fake requests" that we can test our handler methods with: we're sending our fake request to the controller to see what the controller does with this request. Hopefully the controller executes the correct handler method successfully!

The perform() method returns a ResultsAction object, which models a specific action we are expecting, and we can use assertions to test the ResultsAction to see if everything is as we expect it to be.

One method we might invoke upon a ResultsAction is the andExpect() method. This is how we perform an assertion: we give andExpect() a ResultsMatcher object, which models the of assertions we want to match. Confused yet? Don't worry - once you see how it works, it's super easy.

Once we invoke mockMvc.perform(get("/")) to create our fake GET request, we then want to take the ResultsAction and perform assertion on it. So we then invoke andExpect() twice to check 2 assertion

So this whole statement just says, "Perform a fake GET request to the application root and assert that it returns a 200/OK status and successfully loads the index.html page."

What would a fail test look like for this handler method? If the request URL didn't match the pattern, then the handler method wouldn't successfully load the index page:

public void testLoadingIndexFail() throws Exception {

In this test, we expect that we get a status code of 404 (Not Found) if we request the URL with the pattern "/foo"

Another method you could invoke on a ResultsAction is the andDo() method. You give andDo() a ResultsHandler object, which models a generic action you might perform on a response. It's not that exciting, we usually only use it for logging. For example, andDo(print()) will print a log of the results of the request to standard output. It's worth trying just to see what kind of helpful information it prints:

public void testLoadingIndexPass() throws Exception {

Note that this will cause the editor to prompt you to import org.springframework.test.web.servlet.result.MockMvcResultHandlers.print.

Run a JUnit test and see what this testLoadingIndex() does! Check the console so you can see the output that andDo(print()) produces!

Here's sample output that mine showed, in case you don't want to try it yourself:

  HTTP Method = GET
  Request URI = /
   Parameters = {}
      Headers = []
         Body = null
Session Attrs = {}

         Type = ca.sheridancollege.jollymor.sessions.controllers.MainController
       Method = ca.sheridancollege.jollymor.sessions.controllers.MainController#index(Model, HttpSession)

Async started = false
 Async result = null

Resolved Exception:
         Type = null

    View name = index.html
         View = null
    Attribute = inv
        value = Inventory(itemId=0, itemName=null, quantity=0, price=0.0, category=0)
       errors = []

   Attributes = null

       Status = 200
Error message = null
      Headers = [Content-Language:"en", Content-Type:"text/html;charset=UTF-8"]
 Content type = text/html;charset=UTF-8
         Body = [this is where it spewed out the HTML code for the index page
but I didn't feel like manually escaping all the < and > symbols into 
&lt; and &gt; so just imagine all the HTML code for that here]
Forwarded URL = null
Redirected URL = null
      Cookies = []

Another thing you might want to check in the test method is whether or not the model attribute "inv" was successfully added to the model. We can do this using the model().attributeExists(key) method, which is also a ResultsMatcher that asserts true if a model attribute with the key "key" exists. For example, we can update our test method as:

public void testLoadingIndex() throws Exception {

Testing Handlers with Model/Request Parameters

What if we wanted to test the method that adds the new Inventory object to the CopyOnWriteArrayList? Recall that we had a method in our handler in the Sessions exercises that retrieved a bound inventory object from the input form and added it to an array list:

public String addItem(Model model, @ModelAttribute Inventory inv, 
        HttpSession session) {
    Object o = session.getAttribute("inventory");
    List<Inventory> list = null;
    if (o == null) {
        list = new CopyOnWriteArrayList<Inventory>();			
    } else {
        list = (CopyOnWriteArrayList<Inventory>)o;
    session.setAttribute("inventory", list);
    return "inventory.html";

This handler requires a model attribute (the inventory object to add) and a session attribute (the list of inventory objects). How do we test this handler method and give it the values it needs in order to work? We use flash attributes and session attributes.

The flashAttr() method is one of the RequestBuilder methods and it allows you to simulate parameter inputs that your handler method needs (such as @RequestParam params, @ModelAttribute params, etc.)

The sessionAttr() method is also a RequestBuilder method and it allows you to simulate session attributes that your handler method relies on.

Both of these methods belong to RequestBuilder objects, so you invoke them by chaining them onto the get() or post() method (e.g. get(...).flashAttr(...)). Both methods accept a key and value pair representing the parameters you're assigning values to.

For example, our handler method needs a @ModelAttribute Inventory inv. So we can set this using the following code:

Inventory testInventory = new Inventory(32165, "Manos del Uruguay Fino Storm Glass", 
    10, 19.99, 2);
this.mockMvc.perform( post("/addItem").flashAttr("inv", testInventory)) ...

I added an extra space inside the perform( ) parentheses to make it clear that both flashAttr() is invoked on the return value from post().

So we can now start creating a test method to test the functionality of the addItem() method:

public void testAddItem() throws Exception {

    Inventory testInventory = new Inventory(32165, "Manos del Uruguay Fino Storm Glass", 
            10, 19.99, 2);
    this.mockMvc.perform( post("/addItem").flashAttr("inv", testInventory) )

Here we're saying, "perform a POST request to /addItem and include a parameter for the inventory object we constructed, and then assert that the status was 200/OK and the inventory.html page loaded successfully".

If you want to test with session attributes, you can use sessionAttr(). For example, say we want to test this handler method from the database version of the inventory program:

public String doInsert(HttpSession session, @ModelAttribute Inventory inv) {

// if edit attrib doesn't exist, assume false
boolean edit = (session.getAttribute("edit") == null) 
        ? false : (boolean)session.getAttribute("edit");
if (edit)
session.setAttribute("inventory", da.getInventory());
return "invOutput.html";

In this handler, we're relying on a session attribute called "edit": if it contains the boolean value false, we assume we're doing an update, but if it contains the value true, we assume we're doing an add/insert. How can we write unit tests for add and update?

We can test a post request to "/doInsert" and make sure we add a flash attribute for the inventory model attribute and also a session attribute for the "edit" session value:

public void testDoInsert() throws Exception {

    Inventory newItem = new Inventory(32165, "Manos del Uruguay Fino Storm Glass", 
        10, 19.99, 2);
    // test add
    this.mockMvc.perform( post("/doInsert").flashAttr("inv", newItem)
        .sessionAttr("edit", false) )
    // test edit
    this.mockMvc.perform( post("/doInsert").flashAttr("inv", newItem)
        .sessionAttr("edit", true) )

In the first segment of code, we add the model attribute and the session attribute, and make sure the output page is loaded successfully after the insert is performed.

In the second segment, we add the model attribute and session attribute and make sure the output page is loaded successfully after the update is performed.

Tests with Path Variables

In the database version of the Inventory application we have a handler method that process a click on an EDIT link for a particular inventory item:

public String edit(HttpSession session, Model model, @PathVariable int id) {
    // could also grab from inventory list but it's more work
    Inventory i = da.getInventoryById(id);
    model.addAttribute("inv", i);
    session.setAttribute("edit", true);
    return "invIndex.html";

This handler expects a path variable for the record ID of the inventory object that we want to load into the form. Including the path variable value in a test is super easy, just include the parameter in the URL in the get() or post() method, and add an extra argument for the value you want to assign it:

this.mockMvc.perform(get("/edit/{id}", "77633"))

Here we're testing a get request to "/edit/" and using the value 77633 as the {id} @PathVariable value. All together, it might look like this:

public void testEditInventory() throws Exception {
    this.mockMvc.perform(get("/edit/{id}", "77633"))

This will test a GET request to /edit/77633 and assert that it returns a status of 200/OK and successfully loads the invIndex.html file.

There are lots of other things you might want to do in your JUnit tests, so make sure you have a look at the API documentation for the various classes linked above and see what else you might want to do!


1. Add the following methods to your database unit tests in your inventory database exercise:

  1. testGetCategories() asserts that running the database access method that retrieves all the categories returns the right number (e.g. in my data.sql file I added 7 records, so I would assert that invoking da.getCategories() returns the value 7).
  2. testGetInventoryByIdPass() asserts that invoking your database access method that retrieves an inventory object by its ID returns a valid object and not null. Tip: you can use Assert.assertNotEquals() to check and see if the method doesn't return the null object.
  3. testGetInventoryByIdFail() asserts that invoking your database access method that retrieves an inventory object by its ID returns the value null.

2. In Session 4.1 you did the Players form-binding exercise. Add JUnit tests to test the following controller methods (note that this program had no database access so you don't need to worry about auto configuring the database in your test class):

  1. The handler method that loaded the player form page. Assert that the page loads successfully and with a status of 200/OK. Also make sure that the dream team model attribute and the new player attribute are added to the model.
  2. Then handler method that loaded the player output: assert that the page loads successfully with status 200/OK and that the model attributes for the player and the dream team list exist.

TIP: Look through the ResultsMatcher API and look for something that lets you perform assertions on stuff related to the model. For example, how can we tell if a model attribute exists?)