Spring Boot Testing

When it comes to testing, you must follow Test Driven Development principles which have great practices to keep your code and logic clean. One important thing to remember about testing, You must not include logic in your tests.

Rules of Test Driven Development

  1. Don’t write production code without a failing test first.
  2. Write only enough test code to fail.
  3. Write minimal test code to make the failing test pass.

Integration Testing

Integration testing allows you to spin up the server, hook up all components of your application, and you make a call to the endpoint. When this happens, you able to hit the flow of logic the endpoint is meant to execute. You can mock 3rd party api calls if you want.

For example. Let’s say you have an Activity entity. You have an endpoint that exposes a search for activities. This is how it is set up.

 

@RunWith(SpringRunner.class)
@SpringBootTest
@DirtiesContext
public class ControllerTest {

    private MockMvc               mockMvc;

    @Autowired
    private ObjectMapper          objectMapper;

    @Resource
    private WebApplicationContext webApplicationContext;

    @Autowired
    private Filter                springSecurityFilterChain;

    @MockBean
    private UserCacheService      userCacheService;

    @Before
    public void setUp() {

        mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).addFilters(springSecurityFilterChain).build();

        ApiTokenSession apiTokenSession = new ApiTokenSession();
        apiTokenSession.setPrimary(true);
        apiTokenSession.setUserUuid("adminUuid");
        apiTokenSession.setUserAuthorities(Arrays.asList("USER"));
        apiTokenSession.setExpiredAt(DateUtils.addDays(new Date(), 1));
        apiTokenSession.setDeviceId("test_agent");

        when(userCacheService.findApiSessionToken("admin_token")).thenReturn(Optional.of(sideCarApiTokenSessionAdmin));

    }

    @Transactional
    @Test
    public void searchActivity_with_includes() throws Exception {
        String memberUuid = "test-member-uuid-0e045fb7-038a-49e1-b49f-b0b1cec70939";
        Activity activity = new Activity();

        // account,member
        activity.setEntityName("member");
        activity.setDescription("A profile for {{entity}} was created by {{actor}}");
        activity.setEntityUuid(memberUuid);
        activity.setEntityLabel("Test");
        activity.setType("CREATE");

        activityRepository.saveAndFlush(activity);

        activity = new Activity();

        // account,member,expense
        activity.setEntityName("member");
        activity.setDescription("{{actor}} edited member {{entity}}");
        activity.setEntityUuid(memberUuid);
        activity.setEntityLabel("Zen Smith ASO-T");
        activity.setType("EDIT");

        activityRepository.saveAndFlush(activity);

        activity = new Activity();

        // account,member,expense
        activity.setEntityName("member");
        activity.setDescription("{{actor}} deleted member {{entity}}");
        activity.setEntityUuid(memberUuid);
        activity.setEntityLabel("Zen Smith ASO-T");
        activity.setType("DELETE");

        activityRepository.saveAndFlush(activity);
        // @formatter:off

        RequestBuilder requestBuilder = MockMvcRequestBuilders.get("/activity/search")
                .header("token", "admin_token")
                .param("entityName", "member")
                .param("entityUuid", memberUuid)
                .param("includeTypes", "CREATE","DELETE","EDIT")
                .accept(MediaType.APPLICATION_JSON_UTF8);

        MvcResult result = this.mockMvc.perform(requestBuilder)
                .andDo(MockMvcResultHandlers.print())
                .andExpect(MockMvcResultMatchers.status().isOk()).andReturn();
        

        String contentAsString = result.getResponse().getContentAsString();

        CustomPage<Activity> activityResult = objectMapper.readValue(contentAsString, new TypeReference<CustomPage<Activity>>() {});

        assertThat(activityResult).isNotNull();
        
        List<Activity> activities = activityResult.getContent();

        log.info("activities={}",activities);
        
        assertThat(activities).isNotNull();
        assertThat(activities.size()).isGreaterThan(0);
        
       Optional<Activity> optActivity= activities.stream().filter(actvty -> actvty.getType().equals("CREATE") && actvty.getEntityUuid().equals(memberUuid)).findFirst();
        
       assertThat(optActivity.isPresent()).isTrue();
       
       optActivity= activities.stream().filter(actvty -> actvty.getType().equals("EDIT") && actvty.getEntityUuid().equals(memberUuid)).findFirst();
       
       assertThat(optActivity.isPresent()).isTrue();
       
       optActivity= activities.stream().filter(actvty -> actvty.getType().equals("DELETE") && actvty.getEntityUuid().equals(memberUuid)).findFirst();
       
       assertThat(optActivity.isPresent()).isTrue();
        
        // @formatter:on
    }
}

 

Testing endpoints(just the controller web layer) with MockMvc and Mockito

  1. Use MockMvc to mock the endpoint call.
  2. Use @MockBean to mock service interface and Mockiot.when() method to mock service calls.
  3. Use mockMvc.perform to perform endpoint calls.
  4. Use and andExpect() method to verify endpoint response.
  5. Use Mockito.verify() method to verify service calls.
@RunWith(SpringRunner.class)
@WebMvcTest(UserController.class)
public class UserRestControllerTest {

    @Autowired
    private MockMvc     mockMvc;

    @MockBean
    private UserService userService;

    @Test
    public void testUserSave() throws Exception {
        User user = new User("kinga", "kaveinga", 21, "kinga@gmail.com");

        User savedUser = new User(1, "kinga", "kaveinga", 21, "kinga@gmail.com");

        /**
         * thenReturn or doReturn() are used to specify a value to be returned <br/>
         * upon method invocation.
         */
        when(userService.save(user)).thenReturn(savedUser);

        this.mockMvc.perform(post("/users").contentType(MediaType.APPLICATION_JSON).content(user.toJson()))
                .andDo(print())
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.firstName", is("kinga")))
                .andExpect(jsonPath("$.id", is(1)));

        verify(userService, times(1)).save(user);
        verifyNoMoreInteractions(userService);
    }

    @Test
    public void testGetUserById() throws Exception {
        User mockUser = new User("folau", 21, "fkaveinga@gmail.com");

        when(userService.getById(1)).thenReturn(mockUser);

        this.mockMvc.perform(get("/users/1").contentType(MediaType.APPLICATION_JSON))
                .andDo(print())
                .andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
                .andExpect(jsonPath("$.firstName", is("folau")))
                .andExpect(jsonPath("$.age", is(21)));
    }

    @Test
    public void testGetAllUsers() throws Exception {
        List<User> users = Arrays.asList(new User("folaulau", 21, "folaulau@gmail.com"), new User("kinga", 21, "kinga@gmail.com"));
        when(userService.getAll()).thenReturn(users);

        this.mockMvc.perform(get("/users").contentType(MediaType.APPLICATION_JSON))
                .andDo(print())
                .andExpect(status().isOk())
                .andExpect(jsonPath("$[0].firstName", is("folaulau")))
                .andExpect(jsonPath("$[0].age", is(21)));
    }

    @Test
    public void testUpdateUser() throws Exception {
        User user = new User("kinga", "kaveinga", 21, "kinga@gmail.com");

        User savedUser = new User(1, "kinga", "kaveinga", 21, "kinga@gmail.com");

        when(userService.update(user)).thenReturn(savedUser);

        this.mockMvc.perform(patch("/users/update").contentType(MediaType.APPLICATION_JSON).content(user.toJson()))
                .andDo(print())
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.firstName", is("kinga")))
                .andExpect(jsonPath("$.id", is(1)));
    }

    @Test
    public void testRemoveUser() throws Exception {
        long id = 1;
        when(userService.remove(id)).thenReturn(true);

        this.mockMvc.perform(delete("/users/" + id)).andDo(print()).andExpect(status().isOk()).andExpect(MockMvcResultMatchers.content().string("true"));

    }

}

 

Unit Testing business logic with Mockito

  1. Mock objects with the when() method
  2. Verify that methods are called correctly within the logic you are testing. Use the verify() method.
  3. Use AssertThat utility methods to check expected test result.
@Test
public void testSignUp() throws Exception {
    log.info("testSignUp()");
    User user = ConstantUtils.generateUser();

    when(userDAO.save(any(User.class))).thenReturn(user);

    when(userNtcService.sendWelcomeEmail(any(User.class))).thenReturn(true);

    User signedUpUser = userService.signUp(user);

    InOrder inOrder = Mockito.inOrder(userDAO, userNtcService);

    inOrder.verify(userDAO, times(1)).save(userCaptor.capture());

    assertEquals(user, signedUpUser);

    log.info("user captor email={}", userCaptor.getValue().getEmail());

    assertThat(userCaptor.getValue()).isSameAs(signedUpUser);

    log.info("testSignUp() - passed\n\n");
}

Testing DAO(Database Access Object)

  1. Use @Transactional so that database changes will be rolled back.
@RunWith(SpringRunner.class)
@DataJpaTest
public class UserRepositoryTest {

    private final Logger   log       = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private UserRepository userRepository;

    List<User>             testUsers = new ArrayList<User>();

    @Before
    public void setup() {

        for (int i = 0; i < 7; i++) {
            User user = ConstantUtils.generateUser();
            user = userRepository.saveAndFlush(user);
            testUsers.add(user);
            // log.info("user={}",ObjectUtils.toJson(user));
        }

        System.out.println("\n");
    }

    @Transactional
    @Test
    public void testSaveUser() {
        User user = ConstantUtils.generateUser();
        log.info("user={}", ObjectUtils.toJson(user));

        User savedUser = userRepository.saveAndFlush(user);
        log.info("savedUser={}", ObjectUtils.toJson(savedUser));

        assertNotNull(savedUser);

        log.info("savedUser==user -> {}", savedUser == user);

        assertEquals(user, savedUser);

        long count = userRepository.count();

        log.info("count={}", count);

        assertEquals(8, count);

        log.info("\n\ntestSave passed\n");
    }

  
    @Transactional
    @Test
    public void testFindByName() throws InterruptedException, ExecutionException {
        log.info("testFindByName({})", testUsers.get(0).getFirstName());

        String lastName = testUsers.get(0).getLastName();
        List<User> savedUsers = userRepository.findByLastName(lastName);

        log.info(savedUsers.toString());

        assertNotNull(savedUsers);
        assertNotEquals(savedUsers.size(), 0);

        log.info("\n\ntestFindByName passed\n");
    }

    @Transactional
    @Test
    public void testFindByEmail() throws InterruptedException, ExecutionException {
        log.info("testFindByEmail({})", testUsers.get(0).getEmail());
        User savedUser = userRepository.findByEmail(testUsers.get(0).getEmail());

        assertNotNull(savedUser);
        assertNotEquals(savedUser.getId().longValue(), 0);

        System.out.println(savedUser.toString());

        log.info("\n\ntestFindByEmail passed\n");

    }

}

Spy

Spy is a wrapper which is used to override method(s) and return value(s) and ignore logic with those methods.

  1. Use @Spy on the service to spy or Mockito.spy() method.
  2. Use lenient().doReturn(returnedValue).when(service).method();
@Spy
private UserService          spyUserService = new UserServiceImp();

@Test
public void test_SignUpWithPlanAndSpy() throws Exception {
    log.info("test_SignUpWithPlanAndSpy()");
    User user = null;
    double planAmount = 200.0;
    Plan plan = new Plan(1L, planAmount, user);

    planAmount = 500.0;
    Plan savedPlan = new Plan(1L, planAmount, user);

    // This call is not actually made. Execution flow does not get within the signUpForPlan(check the log within the
    // method)
    lenient().doReturn(savedPlan).when(spyUserService).signUpForPlan(plan);

    assertThat(savedPlan.getAmount()).isEqualTo(planAmount);

    log.info("test_SignUpWithPlanAndSpy() - passed!");
}

 

Source code on Github

 




Subscribe To Our Newsletter
You will receive our latest post and tutorial.
Thank you for subscribing!

required
required


Leave a Reply

Your email address will not be published. Required fields are marked *