Understanding Spring Test Types in Microservice Projects

Understanding Spring Test Types in Microservice Projects

In a real-world Spring Boot microservice architecture, choosing the right kind of test can drastically affect test speed, confidence, and maintainability. This guide breaks down key test types used in Spring applications, their purposes, and best practices.


✅ 1. Unit Test (Pure Java)

Purpose: Test a single class/method in isolation without Spring context.

Characteristics:

  • No Spring involved

  • Fastest and most isolated

  • Good for testing: Utils, DTOs, Mappers

Example:

class JwtUtilTest {
    private final JwtUtil jwtUtil = new JwtUtil("secret", 3600);

    @Test
    void testGenerateAndValidateToken() {
        String token = jwtUtil.generateToken("user123");
        assertTrue(jwtUtil.validateToken(token));
    }
}

✅ 2. Unit Test with Spring Context

Purpose: Test logic that depends on Spring-managed beans or environment.

Use When:

  • You need @Autowired services or components

  • Spring Security context or configuration is involved

Example: CustomUserDetailServiceIT

@SpringBootTest
@AutoConfigureWebClient
@Import(RestTemplateTestConfig.class)
class CustomUserDetailServiceIT {

    @Autowired
    private CustomUserDetailService customUserDetailService;

    @Autowired
    private RestTemplate restTemplate;

    private MockRestServiceServer mockServer;

    @BeforeEach
    void setup() {
        mockServer = MockRestServiceServer.createServer(restTemplate);
    }

    @Test
    void loadUserByUsername_shouldReturnValidUserDetails_whenUserExists() {
        String testEmail = "test@example.com";
        MemberDTO mockMember = new MemberDTO();
        mockMember.setEmail(testEmail);
        mockMember.setPassword("securePassword");
        mockMember.setRole("ROLE_USER");

        ServiceResponse<MemberDTO> response = new ServiceResponse<>();
        response.setSuccess(true);
        response.setData(mockMember);

        String jsonResponse;
        try {
            jsonResponse = new MappingJackson2HttpMessageConverter()
                    .getObjectMapper()
                    .writeValueAsString(response);
        } catch (JsonProcessingException e) {
            throw new RuntimeException("Failed to serialize mock response", e);
        }

        mockServer.expect(requestTo("http://MEMBER-SERVICE/member/findMemberByEmail"))
                .andExpect(method(HttpMethod.POST))
                .andExpect(content().string(testEmail))
                .andRespond(withSuccess(jsonResponse, MediaType.APPLICATION_JSON));

        UserDetails userDetails = customUserDetailService.loadUserByUsername(testEmail);

        assertThat(userDetails.getUsername()).isEqualTo(testEmail);
        assertThat(userDetails.getPassword()).isEqualTo("securePassword");
        assertThat(userDetails.getAuthorities())
                .extracting("authority")
                .containsExactly("ROLE_USER");

        mockServer.verify();
    }
}

✅ 1. Unit Test (Pure Java, MapStruct Mapper)

Purpose: Test mapper logic without Spring context

Characteristics:

  • Uses Mappers.getMapper(...)

  • Does not require @SpringBootTest

  • Very fast and self-contained

Example: MemberMapperSpringTest.testUserDetailToMemberDTO()

class MemberMapperSpringTest {

    private final MemberMapper memberMapper = Mappers.getMapper(MemberMapper.class);

    @Test
    void testUserDetailToMemberDTO() {
        UserDetails userDetails = new UserDetails() {
            @Override public String getUsername() { return "test@example.com"; }
            @Override public String getPassword() { return "securePassword"; }
            @Override public Collection<? extends GrantedAuthority> getAuthorities() {
                return List.of(new SimpleGrantedAuthority("ROLE_USER"));
            }
            @Override public boolean isAccountNonExpired() { return true; }
            @Override public boolean isAccountNonLocked() { return true; }
            @Override public boolean isCredentialsNonExpired() { return true; }
            @Override public boolean isEnabled() { return true; }
        };

        MemberDTO memberDTO = memberMapper.userDetailToMemberDTO(userDetails);

        assertEquals(userDetails.getUsername(), memberDTO.getUsername());
        assertEquals(userDetails.getPassword(), memberDTO.getPassword());
    }
}
```java
class MemberMapperSpringTest {

    private final MemberMapper memberMapper = Mappers.getMapper(MemberMapper.class);

    @Test
    void testUserDetailToMemberDTO() {
        UserDetails userDetails = new UserDetails() {
            @Override public String getUsername() { return "test@example.com"; }
            @Override public String getPassword() { return "securePassword"; }
            @Override public Collection<? extends GrantedAuthority> getAuthorities() {
                return List.of(new SimpleGrantedAuthority("ROLE_USER"));
            }
            @Override public boolean isAccountNonExpired() { return true; }
            @Override public boolean isAccountNonLocked() { return true; }
            @Override public boolean isCredentialsNonExpired() { return true; }
            @Override public boolean isEnabled() { return true; }
        };

        MemberDTO memberDTO = memberMapper.userDetailToMemberDTO(userDetails);

        assertEquals(userDetails.getUsername(), memberDTO.getUsername());
        assertEquals(userDetails.getPassword(), memberDTO.getPassword());
    }
}

✅ 3. Mock Integration Test

Purpose: Simulate external service calls (e.g., REST) using mocks.

Use Tools Like:

  • MockRestServiceServer (RestTemplate)

  • @WebMvcTest + MockMvc

Use When:

  • You want to test integration logic without hitting real services

  • You want fast feedback with controlled responses

Example: CustomUserDetailServiceIT

@SpringBootTest
@AutoConfigureWebClient
@Import(RestTemplateTestConfig.class)
class CustomUserDetailServiceIT {

    @Autowired
    private CustomUserDetailService customUserDetailService;

    @Autowired
    private RestTemplate restTemplate;

    private MockRestServiceServer mockServer;

    @BeforeEach
    void setup() {
        mockServer = MockRestServiceServer.createServer(restTemplate);
    }

    @Test
    void loadUserByUsername_shouldReturnValidUserDetails_whenUserExists() {
        // Setup mock response to MEMBER-SERVICE
        mockServer.expect(requestTo("http://MEMBER-SERVICE/member/findMemberByEmail"))
                  .andRespond(withSuccess(...));

        UserDetails userDetails = customUserDetailService.loadUserByUsername("test@example.com");

        assertEquals("test@example.com", userDetails.getUsername());
    }
}

✅ 4. Full Integration Test

Purpose: Run full Spring context and test real interactions between components (REST, DB, Eureka).

Use When:

  • You want to validate behavior across real modules

  • You rely on Eureka, DB, or Cloud Config

Example: AuthControllerIT

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
class AuthControllerIT {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void testRegister() throws Exception {
        mockMvc.perform(post("/auth/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content("{"email":"test@example.com"}"))
                .andExpect(status().isCreated());
    }
}

✅ 5. End-to-End (E2E) Test

Purpose: Validate full user flow across services, databases, gateways, and possibly frontend.

Tools:

  • TestContainers

  • Docker Compose

  • Postman / Newman

  • Cypress (frontend)

Example:

  • User registration through API Gateway hitting multiple microservices


Summary Table

Test TypeUses Spring?Uses Real Beans?External Systems?SpeedPurpose
Unit Test (Pure)❌ No❌ No❌ No⚡ FastLogic, util, mapper
Unit + Spring Context✅ Yes✅ Yes❌ No⏳ MediumSpring-feature logic
Mock Integration Test✅ Yes✅ Yes❌ Mocked⏳ MediumExternal API clients (mocked)
Full Integration Test✅ Yes✅ Yes✅ Yes⏱ SlowInter-module validation
End-to-End Test✅ Full stack✅ All services✅ Yes⏰ Very SlowUser scenario simulation

Conclusion

Choosing the right type of test is key to balancing speed, confidence, and coverage. Start with fast unit tests, mock where appropriate, and reserve integration tests for validating system behavior.

✨ Rule of Thumb: “Don’t use Spring if you don’t have to.”

Let me know if you’d like this turned into a Markdown blog post or visual diagram.

Leave a Comment

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