Skip to content

Lab 6 - Get an Existing Order By ID

In this lab you'll create a GET mapping to support retrieving orders that are in the repository.

A. Create Failing Integration Test

You'll start with a couple of failing integration tests. Copy this into a new Test class called MealOrderApiIntegrationTest:

package com.welltestedlearning.mealkiosk.api;

import com.welltestedlearning.mealkiosk.adapter.MealBuilder;
import com.welltestedlearning.mealkiosk.domain.MealOrder;
import com.welltestedlearning.mealkiosk.domain.MealOrderRepository;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class MealOrderApiIntegrationTest {

  @Autowired
  private MockMvc mockMvc;

  @Autowired
  private MealOrderRepository mealOrderRepository;

  @Test
  public void postCreatesNewMealOrder() throws Exception {
    mockMvc.perform(post("/api/mealorder")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"burger\": \"cheese\", \"drinkSize\": \"large\", \"friesSize\": \"regular\"}"))
           .andExpect(status().isOk())
           .andExpect(jsonPath("$.id").value("0"));
  }

  @Test
  public void getWithIdReturnsPriceAndId() throws Exception {
    MealOrder mealOrder = MealBuilder.builder().burger("cheese").build();
    mealOrder.setId(1L); // force id to be 1 for this test
    mealOrderRepository.save(mealOrder);

    mockMvc.perform(get("/api/mealorder/1"))
           .andExpect(status().isOk())
           .andExpect(jsonPath("$.id").value(1))
           .andExpect(jsonPath("$.price").value("6"));
  }
}

Try running this test class: the first test (testing the POST mapping) will pass, but the second test should fail, because you haven't yet written code for the GET mapping. We'll do that next.

B. Create Failing Unit Test

Before we write the code for the GET mapping, let's have a failing Unit Test.

  1. Open up MealOrderApiControllerTest
  2. Add this test:

    @Test
    public void getReturnsExistingMealOrderById() throws Exception {
      // Given: a repository with a meal order saved
      MealOrderRepository mealOrderRepository = new MealOrderRepository();
      MealOrder mealOrder = MealBuilder.builder().burger("cheese").build();
      MealOrder savedMealOrder = mealOrderRepository.save(mealOrder);
    
      // And: an API Controller with the repository
      MealOrderApiController controller = new MealOrderApiController(mealOrderRepository);
    
      // When: we do a GET for the ID
      Long savedMealOrderId = savedMealOrder.getId();
      MealOrderResponse response = controller.findMealOrder(savedMealOrderId);
    
      // Then: we expect the response to have the same ID and price
      assertThat(response.getId())
          .isEqualTo(savedMealOrderId.toString());
      assertThat(response.getPrice())
          .isEqualTo(mealOrder.price());
    }
    

Now add add the minimum implementation to make the test compile

  1. Open up the MealOrderApiController class
  2. Add the following method:
    @GetMapping("/api/mealorder/{id}")
    public MealOrderResponse findMealOrder(@PathVariable("id") Long id) {
      return null;
    }
    
  3. Run this test: it should fail (with a NullPointerException)

C. Make Test Pass: GET Mapping Method

Run All Tests

If you run all of the tests, you should have two failing tests: the failing Unit Test and the failing Integration Test. If you have more than two failing tests, fix them before moving on.

Now that you have failing tests, write the code to make the tests pass.

In the MealOrderApiController class, inside of the findMealOrder method, fill in the implementation steps:

  1. Lookup the meal order in the repository by the ID that was passed in
  2. Transform the MealOrder to a MealOrderResponse
  3. Return the response object
  4. Run all tests

D. Handle Non-Existent ID

What if the requested ID isn't found? We'd like to get a 404 (Not Found) status code back. Let's add an Integration Test that checks for that.

  1. Open MealOrderApiIntegrationTest and add the following test
    @Test
    public void getWithNonExistentIdReturnsNotFound() throws Exception {
      mockMvc.perform(get("/api/mealorder/999"))
             .andExpect(status().isNotFound());
    }
    
  2. Run it and it should fail.

    How Did It Fail?

    What was the root cause of the failure, i.e., which Exception? It's a good idea to get used to looking at the stack trace for the failure reason.

In order to return a Not Found status code, you will throw a special exception.

  1. Create a new exception class (in the api package) called NoSuchOrderException:

    import org.springframework.http.HttpStatus;
    import org.springframework.web.bind.annotation.ResponseStatus;
    
    @ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "No order with that ID was found.")
    public class NoSuchOrderException extends RuntimeException {
    }
    
  2. Open up the MealOrderApiController and add code inside the findMealOrder method that throws the exception if the order wasn't found.

    Throwing Exception

    To throw the exception, you can do: throw new NoSuchOrderException()

  3. Run the tests, they should all pass.

  4. Also try out the API using Postman/curl and the browser.