Skip to content

Instantly share code, notes, and snippets.

@roberthamel
Last active May 1, 2018 19:21
Show Gist options
  • Save roberthamel/a0027140fe40087ecd059a5bf8580f01 to your computer and use it in GitHub Desktop.
Save roberthamel/a0027140fe40087ecd059a5bf8580f01 to your computer and use it in GitHub Desktop.

Mockito - Part 2

01: Creating a Hello World Controller

// [...]/controllers/HelloWorldController.java

@RestController
public class HelloWorldController {
  
  @GetMapping("/hello-world")
  public String helloWorld() {
    return "Hello World";
  }
  
}

02: Using Mock Mvc to test Hello World Controller

// [...]/test/[...]/controllers/HelloWorldControllerTest.java

@RunWith(SpringRunner.class)
@WebMvcTest(HelloWorldController.class)
public class HelloWorldControllerTest {
  
  @Autowired
  private MockMvc mockMvc;
  
  @Test
  public void helloWorld_basic() throws Exception {
    
    RequestBuilder request = MockMvcRequestBuilders
        .get("/hello-world")
        .accept(MediaType.APPLICATION_JSON);
    
    // call "/hello-world" GET application/json
    MockMvc result = mockMvc.perform(request).andReturn();
    
    // verify "Hello World"
    assertEquals("Hello World", result.getResponse().getContentAsString());
  }
  
}

03: Using Response Matchers to check status and content

// [...]/test/[...]/controllers/HelloWorldControllerTest.java

@RunWith(SpringRunner.class)
@WebMvcTest(HelloWorldController.class)
public class HelloWorldControllerTest {
  
  @Autowired
  private MockMvc mockMvc;
  
  @Test
  public void helloWorld_basic() throws Exception {
    
    RequestBuilder request = MockMvcRequestBuilders
        .get("/hello-world")
        .accept(MediaType.APPLICATION_JSON);
    
    // call "/hello-world" GET application/json
    MockMvc result = mockMvc.perform(request)
        .andExpect(status().isOk()) // <-- check status is ok
        .andExpect(content().string("Hello World")) // <-- check content
        .andReturn();
    
    // verify "Hello World"
    // assertEquals("Hello World", result.getResponse().getContentAsString()); // <-- no longer needed
  }
  
}

04: Creating a Basic REST Service in Item Controller

// [...]/models/Item.java

public class Item {
  
  private int id;
  private String name;
  private int price;
  private int quantity;
  
  public Item(int id, String name, int price, int quantity) {
    this.id = id;
    this.name = name;
    this.price = price;
    this.quantity = quantity;
  }
  
  // ...getters and setters
  
  public String toString() {
    return String.format("Item[%d, %s, %d, %d]", id, name, price, quantity);
  }
}
// [...]/controllers/ItemController.java

@RestController
public class ItemController {
  
  @GetMapping("/dummy-item")
  public Item dummyItem() {
    return new Item(1, "Ball", 10, 100);
  }
  
}

05: Unit Testing Item Controller and Basic JSON Assertions

// [...]/test/[...]/models/ItemControllerTest.java

@RunWith(SpringRunner.class)
@WebMvcTest(ItemController.class)
public class HelloWorldControllerTest {
  
  @Autowired
  MockMvc mockMvc;
  
  @Test
  public void dummyItem_basic() throws Exception {
    
    RequestBuilder request = MockMvcRequestBuilders
        .get("/dummy-item")
        .accept(MediaType.APPLICATION_JSON);
    
    // call "/dummy-item" GET application/json
    MockMvc result = mockMvc.perform(request)
        .andExpect(status().isOk()) // <-- check status is ok
        .andExpect(content().json(
            "{\"id\":1,\"name\":\"ball\",\"price\":10,\"quantity\":100}" // <-- check content
         ))
        .andReturn();
  
  }
  
}

06: Digging deeper into JSON Assert

JSONAssert.assertEquals(expected, actual, isStrict);
// [...]/test/[...]/spike/JsonAssertTest.java

public class JsonAssertTest {
  
  String actual = "{\"id\":1,\"name\":\"ball\",\"price\":10,\"quantity\":100}";
  
  @Test
  public void jsonAssert_StrictTrue_ExactMatchExceptForSpaces() throws JSONException {
    String expected = "{\"id\":1,\"name\":\"ball\",\"price\":10,\"quantity\":100}";
    JSONAssert.assert(expected, actual, true);
  }
  
  @Test
  public void jsonAssert_StrictFalse() throws JSONException {
    String expected = "{\"id\":1,\"name\":\"ball\",\"price\":10,\"quantity\":100}";
    JSONAssert.assert(expected, actual, false);
  }
  
  @Test
  public void jsonAssert_WithoutEscapeCharacters() throws JSONException {
    String expected = "{id:1,name:ball,price:10,quantity:100}";
    JSONAssert.assert(expected, actual, false);
  }
}

07: Writing a REST Service talking to Business Layer

// [...]/services/BusinessService.java

@Service
public class ItemBusinessService {
  public Item retrieveHardcodedItem() {
    return new Item(1, "Ball", 10, 100);
  }
}
// [...]/controllers/ItemController.java

@RestController
public class ItemController {
  
  @Autowired
  private ItemBusinessService businessService;
  
  // ...
  
  @GetMapping("/item-from-business-service")
  public Item itemFromBusinessService() {
    return businessService.retrieveHardcodedItem();
  }
  
}

08: Writing Unit Test for REST Service mocking Business Layer

// [...]/test/[...]/models/ItemControllerTest.java

@RunWith(SpringRunner.class)
@WebMvcTest(ItemController.class)
public class HelloWorldControllerTest {
  
  @Autowired
  private MockMvc mockMvc;
  
  @MockBean
  private ItemBusinessService businessService;
  
  @Test
  public void dummyItem_basic() throws Exception {
    // ...
  }
  
  @Test
  public void itemFromBusinessService() {
    when(businessService.retrieveHardcodedItem()).thenReturn(
      new Item(2, "Item 2", 10, 10)  
    );
    
    RequestBuilder request = MockMvcRequestBuilders
        .get("/item-from-business-service")
        .accept(MediaType.APPLICATION_JSON)
        
    MockMvc result = mockMvc.perform(request)
        .andExpect(status().isOk())
        .andExpect(content().json("{id:2,name:Item 2,price:10,quantity:10}"))
        .andReturn();
  }
}

09: Prepare Data Layers with JPA, Hibernate and H2

// pom.xml
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
  <groupId>com.h2database</groupId>
  <artifactId>h2</artifactId>
</dependency>

10: Create Item Entity and Populate data with data.sql

// [...]/models/Item.java

@Entity
public class Item {
  
  @Id
  private int id;
  private String name;
  private int price;
  private int quantity;
  
  @Transient
  private int value;
  
  public Item() {}
  
  public Item(int id, String name, int price, int quantity) {
    this.id = id;
    this.name = name;
    this.price = price;
    this.quantity = quantity;
  }
  
  // ...getters and setters
  
  public String toString() {
    return String.format("Item[%d, %s, %d, %d]", id, name, price, quantity);
  }
}
# application.properties
spring.jpa.show-sql=true

# jdbc url: jdbc:h2:mem:testdb
spring.h2.console.enabled=true
-- data.sql
insert into item (id, name, price, quantity) values (10001, 'item1', 10, 20);
insert into item (id, name, price, quantity) values (10002, 'item2', 5, 10);
insert into item (id, name, price, quantity) values (10003, 'item3', 15, 2);

11: Create a RESTful Service talking to the database

// [...]/repositories/ItemRepository.java

public interface ItemRepository extends JpaRepository<Item, Integer> {
  
}
// [...]/services/BusinessService.java

@Service
public class ItemBusinessService {
  
  @Autowired
  private ItemRepository repository;
  
  public Item retrieveHardcodedItem() {
    // ...
  }
  
  public List<Item> retrieveAllItems() {
    List<Item> items = repository.findAll();
    
    for (Item item : items) {
      item.setValue(item.getPrice() * item.getQuantity());
    }
    
    return items;
  }
}
// [...]/controllers/ItemController.java

@RestController
public class ItemController {
  
  @Autowired
  private ItemBusinessService businessService;
  
  // ...
  
  @GetMapping("/all-items-from-database")
  public List<Item> retrieveAllItems() {
    return businessService.retrieveAllItems();
  }
}

12: Writing Unit Test for Web Layer - Controller - Using Mock MVC

// [...]/test/[...]/models/ItemControllerTest.java

@RunWith(SpringRunner.class)
@WebMvcTest(ItemController.class)
public class HelloWorldControllerTest {
  
  @Autowired
  private MockMvc mockMvc;
  
  @MockBean
  private ItemBusinessService businessService;
  
  // ...
  
  @Test
  public void retrieveAllItems_basic() throws Exception {
    when(businessService.retrieveAllItems()).thenReturn(
      Arrays.asList(
        new Item(2, "Item2", 10, 10),
        new Item(3, "Item3", 20, 20)
      );
    );
    
    RequestBuilder request = MockMvcRequestBuilders
        .get("/all-items-from-database")
        .accept(MediaType.APPLICATION_JSON);
        
    MvcResult result = mockMvc.perform(request)
        .andExpect(status().ok())
        .andExpect(content().json("[{id:2,name:Item2,price:10},{id:3,name:Item3,price:20}]"))
        .andReturn();
  }
}

13: Exercise & Solution - Writing Unit Test for Business Layer - Mocking

// [...]/test/[...]/services/ItemBusinessServiceTest.java

@RunWith(MockitoJUnitRunner.class)
public class ItemBusinessServiceTest {
  
  @InjectMocks
  private ItemBusinessService business;
  
  @Mock
  private ItemRepository repository;
  
  @Test
  public void retrieveAllItems() {
    when(repository.findAll()).thenReturn(
      Arrays.asList(
        new Item(2, "Item2", 10, 10),
        new Item(3, "Item3", 20, 20)
      )  
    );
    
    List<Item> items = business.retrieveAllItems();
    
    assertEquals(100, items.get(0).getValue());
    assertEquals(400, items.get(1).getValue());
  }
}

14: Writing Unit Test for Data Layer - Data JPA Test

// [...]/test/[...]/repositories/ItemRepositoryTest.java

// depends on what lives inside the data.sql file
// generally you would only write these tests if you want to add your own custom repository methods
// debate-able because it you are still technically testing spring framework

// you can also define your data.sql file in the src/test/resources folder to override the test data

@RunWith(SpringRunner.class)
@DataJpaTest
public class ItemRepositoryTest {
  
  @Autowired
  private ItemRepository repository;
  
  @Test
  public void testFindAll() {
    List<Item> items = repository.findAll();
    assertEquals(3, items.size());
  }
}

15: Writing an Integration Test using @SpringBootTest

 // [...]/test/[...]/controllers/ItemControllerIT.java
 
 // IT: Integration Test
 
 @RunWith(SpringRunner.class)
 @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
 public class ItemControllerIT {
   
   @Autowired
   private TestRestTemplate restTemplate;
   
   @Test
   public void contextLoads() throws JSONException {
     String response = this.restTemplate.getForObject("/all-items-from-database", String.class);
     JSONAssert.assertEquals("[{id:10001},{id:10002},{id:10003}]", response, false);
   }
   
 }

16: Using @MockBean to mock out dependencies you do not want to talk to!

@MockBean
private ItemRepository repository;

@Test
public void contextLoads() {
  when(...).thenReturn(...);
}

17: Separate Test Configuration

# src/test/resources/application.properties
# this configuration will override what resides in src/main/resources/application.properties
spring.jpa.show-sql=false
spring.h2.console.enabled=false

Or...

// above the test class
@TestPropertySource(locations = {"classpath:test-configuration.properties"})
#  src/test/resources/test-configuration.properties
# ...

18: Writing Unit Tests for Other Request Methods

// post request
RequestBuilder requestBuilder = MockMvcRequestBuilders.post("/items")
    .accept(MediaType.APPLICATION_JSON)
    .content("{id:1,name:Ball,price:10,quantity:100}")
    .contentType(MediaType.APPLICATION_JSON);
    
MvcResult result = mockMvc.perform(requestBuilder)
    .andExpect(status().isCreated())
    .andExpect(header().string("location", containsString("/item/")))
    .andReturn();

19: Refactor SomeBusinessImpl to use Functional Programming

// procedural
public int calculateSum(int[] data) {
  int sum = 0;
  for (int value : data) {
    sum += value;
  }
  return sum;
}

// functional
public int calculateSum(int... data) {
  return Arrays.stream(data).reduce(Integer::sum).orElse(0);
}

20: Better Assertions with Hamcrest - HamcrestMatcherTest

// [...]/test/[...]/spike/HamcrestMatchersTest.java

public class HamcrestMatchersTest {
  
  @Test
  public void learning() {
    List<Integer> numbers = Arrays.asList(12, 15, 45);
    
    assertThat(numbers, hasSize(3));
    assertThat(numbers, hasItems(12,45));
    assertThat(numbers, everyItem(greaterThan(10)));
    assertThat(numbers, everyItem(lessThan(100)));
    
    assertThat("", isEmptyString());
    assertThat("ABCDE", containsString("BCD"));
    assertThat("ABCDE", startsWith("ABC"));
    assertThat("ABCDE", endsWith("CDE"));
  }
}

21: Better Assertions with AssertJ - AssertJTest

// [...]/test/[...]/spike/AssertJTest.java

public class AssertJTest {
  
  @Test
  public void learning() {
    List<Integer> numbers = Arrays.asList(12,15,45);
    
    assertThat(numbers)
      .hasSize(3)
      .contains(12,15)
      .allMatch(x -> x > 10)
      .allMatch(x -> x < 100)
      .noneMatch(x -> x < 0);
      
    assertThat("").isEmpty();
    assertThat("ABCDE")
      .contains("BCD")
      .startsWith("ABC")
      .endsWith("CDE");
  }
}

22: Better Assertions with JSONPath - JSONPathTest

// [...]/test/[...]/spike/JsonPathTest.java

public class JsonPathTest {
  
  @Test
  public void learning() {
    String responseFromService = "[" +
      "{\"id\": 10000, \"name\": \"Pencil\", \"quantity\": 5}," +
      "{\"id\": 10001, \"name\": \"Pen\", \"quantity\": 15}," +
      "{\"id\": 10002, \"name\": \"Eraser\", \"quantity\": 10}" +
    "]";
    
    DocumentContext context = JsonPath.parse(responseFromService);
    int length = context.read("$.length");
    assertThat(length).isEqualTo(3);
    
    List<Integer> ids = context.read("$..id").toString();
    assertThat(ids).containsExactly(10000,10001,10002);
    
    context.read("$.[1]").toString();
    context.read("$.[0:2]").toString();
    context.read("$.[?(@.name=='Eraser')]").toString();
    context.read("$.[?(@.quantity==5)]").toString();
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment