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
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
@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
@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)
@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.
@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!"); }