Testing Spring Applications

Back

Loading concept...

πŸ§ͺ Testing Spring Applications: Your Safety Net for Perfect Code

Imagine you’re a chef preparing a grand feast. Before serving each dish, you taste it to make sure it’s delicious. Testing in Spring is exactly like that – tasting your code before serving it to users!


🎯 The Big Picture

Testing Spring applications is like having a robot assistant that checks your work automatically. Instead of clicking through your app 100 times to make sure everything works, you write tests that do it for you – in seconds!

Why Testing Matters

Think of building software like building a LEGO castle:

  • Without tests: You build the whole castle, then it falls apart. Where did it break? Who knows! 😰
  • With tests: You check each section as you build. If something breaks, you know exactly where! 😊

🎭 @WebMvcTest: Testing Your Web Layer Only

What Is It?

@WebMvcTest is like testing just the front door of your house, without checking the entire house.

Real-World Analogy:

A restaurant tests if the waiter takes orders correctly – without actually cooking food yet!

How It Works

@WebMvcTest(UserController.class)
class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void shouldReturnUserName()
        throws Exception {

        mockMvc.perform(get("/user/1"))
            .andExpect(status().isOk())
            .andExpect(content()
                .string("John"));
    }
}

What Gets Loaded?

graph TD A["πŸ§ͺ @WebMvcTest"] --> B["βœ… Controllers"] A --> C["βœ… Filters"] A --> D["βœ… Web Config"] A --> E["❌ Services"] A --> F["❌ Repositories"] A --> G["❌ Database"]

Key Point: Only the web layer loads – making tests super fast! ⚑


πŸŽͺ MockMvc: Your Fake Web Browser

What Is It?

MockMvc pretends to be a web browser. It sends requests to your controllers without starting a real server.

Analogy:

It’s like a flight simulator – pilots practice flying without leaving the ground!

Common Actions

Action What It Does
perform() Send a request
andExpect() Check the response
andDo() Do something extra

Example: Testing a POST Request

@Test
void shouldCreateUser()
    throws Exception {

    String json = """
        {"name": "Alice",
         "email": "alice@test.com"}
        """;

    mockMvc.perform(
        post("/users")
            .contentType(APPLICATION_JSON)
            .content(json))
        .andExpect(status()
            .isCreated())
        .andExpect(jsonPath("$.name")
            .value("Alice"));
}

Response Checks You Can Make

graph TD A["MockMvc Assertions"] --> B["status - HTTP code"] A --> C["content - body text"] A --> D["jsonPath - JSON fields"] A --> E["header - HTTP headers"] A --> F["cookie - cookies"]

🎭 @MockBean: Creating Fake Helpers

What Is It?

@MockBean creates a pretend version of a service. It acts like the real thing but you control what it returns.

Analogy:

Testing a movie scene with a stunt double instead of the real actor!

Why Use It?

Your controller needs a service. But in testing:

  • You don’t want to call the real database
  • You want to control exactly what happens
  • You want tests to be fast and predictable

Example

@WebMvcTest(UserController.class)
class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private UserService userService;

    @Test
    void shouldGetUser()
        throws Exception {

        // Tell the mock what to return
        User fakeUser = new User(
            1L, "Bob", "bob@test.com"
        );

        when(userService.findById(1L))
            .thenReturn(fakeUser);

        // Test the controller
        mockMvc.perform(get("/users/1"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.name")
                .value("Bob"));
    }
}

How It Works

graph LR A["Test"] --> B["Controller"] B --> C["🎭 Mock Service"] C --> D["Returns fake data"] D --> B B --> A

The controller thinks it’s talking to the real service, but it’s not!


πŸ—„οΈ @DataJpaTest: Testing Your Database Layer

What Is It?

@DataJpaTest tests only your database code – repositories and entities.

Analogy:

Testing if your filing cabinet stores and retrieves papers correctly – without caring about who puts papers in!

What Gets Loaded?

graph TD A["πŸ§ͺ @DataJpaTest"] --> B["βœ… Repositories"] A --> C["βœ… Entities"] A --> D["βœ… Test Database"] A --> E["❌ Controllers"] A --> F["❌ Services"] A --> G["❌ Web Layer"]

Example

@DataJpaTest
class UserRepositoryTest {

    @Autowired
    private UserRepository repo;

    @Autowired
    private TestEntityManager em;

    @Test
    void shouldFindByEmail() {
        // Create test data
        User user = new User();
        user.setName("Charlie");
        user.setEmail("c@test.com");
        em.persist(user);
        em.flush();

        // Test the query
        User found = repo
            .findByEmail("c@test.com");

        // Check result
        assertThat(found.getName())
            .isEqualTo("Charlie");
    }
}

Key Features

Feature Benefit
Uses H2 in-memory DB Fast, no setup
Auto-rollback Each test starts fresh
Only loads JPA Lightning fast

🌐 REST Test Clients: Testing from Outside

What Are They?

REST test clients let you test your API like a real user would – by making actual HTTP calls.

Option 1: TestRestTemplate

@SpringBootTest(
    webEnvironment = RANDOM_PORT
)
class ApiTest {

    @Autowired
    private TestRestTemplate rest;

    @Test
    void shouldGetUsers() {
        ResponseEntity<String> response =
            rest.getForEntity(
                "/api/users",
                String.class
            );

        assertThat(response.getStatusCode())
            .isEqualTo(HttpStatus.OK);
    }
}

Option 2: WebTestClient (Reactive)

@SpringBootTest(
    webEnvironment = RANDOM_PORT
)
class ReactiveApiTest {

    @Autowired
    private WebTestClient client;

    @Test
    void shouldGetUser() {
        client.get()
            .uri("/api/users/1")
            .exchange()
            .expectStatus().isOk()
            .expectBody()
            .jsonPath("$.name")
            .isEqualTo("Dana");
    }
}

Comparison

graph TD A["REST Test Clients"] --> B["TestRestTemplate"] A --> C["WebTestClient"] B --> D["Traditional apps"] B --> E["Blocking calls"] C --> F["Reactive apps"] C --> G["Fluent API"] C --> H["Works with both!"]

πŸ”— Integration Testing: The Full Picture

What Is It?

Integration testing checks if all parts work together – like a full dress rehearsal before opening night!

Analogy:

Testing a whole car, not just the engine or wheels separately. Does everything work together smoothly?

The Magic Annotation

@SpringBootTest

This loads your entire application – everything!

Example: Full Stack Test

@SpringBootTest(
    webEnvironment = RANDOM_PORT
)
class FullIntegrationTest {

    @Autowired
    private TestRestTemplate rest;

    @Autowired
    private UserRepository userRepo;

    @Test
    void shouldCreateAndRetrieve() {
        // Create via API
        User newUser = new User(
            null, "Eve", "eve@test.com"
        );

        ResponseEntity<User> created =
            rest.postForEntity(
                "/api/users",
                newUser,
                User.class
            );

        assertThat(created.getStatusCode())
            .isEqualTo(CREATED);

        // Verify in database
        Long id = created.getBody().getId();
        Optional<User> inDb =
            userRepo.findById(id);

        assertThat(inDb).isPresent();
        assertThat(inDb.get().getName())
            .isEqualTo("Eve");
    }
}

What Gets Tested?

graph TD A["πŸ§ͺ Integration Test"] --> B["Controller"] B --> C["Service"] C --> D["Repository"] D --> E["Database"] E --> D D --> C C --> B B --> A style A fill:#e1f5fe style E fill:#c8e6c9

Test Isolation with @Transactional

@SpringBootTest
@Transactional
class IsolatedIntegrationTest {
    // Each test runs in a transaction
    // and rolls back at the end!
    // Database stays clean!
}

🎯 Quick Reference: Which Test for What?

Testing Goal Use This Speed
Controller logic @WebMvcTest ⚑⚑⚑
Database queries @DataJpaTest ⚑⚑⚑
API responses TestRestTemplate ⚑⚑
Everything together @SpringBootTest ⚑

πŸ† The Testing Pyramid

graph TD A["πŸ”Ί Few&lt;br&gt;Integration Tests"] --> B["πŸ”Ά Some&lt;br&gt;Slice Tests"] B --> C["🟒 Many&lt;br&gt;Unit Tests"]

Key Insight:

  • Unit tests (bottom): Fast, many, test small pieces
  • Slice tests (middle): Test layers (@WebMvcTest, @DataJpaTest)
  • Integration tests (top): Slow, few, test everything together

πŸŽ‰ You Did It!

Now you know how to:

βœ… Test controllers with @WebMvcTest and MockMvc βœ… Create fake services with @MockBean βœ… Test database code with @DataJpaTest βœ… Make real API calls with TestRestTemplate and WebTestClient βœ… Run full integration tests with @SpringBootTest

Remember: Good tests are like a safety net. They catch bugs before your users do! πŸ¦Έβ€β™‚οΈ

β€œCode without tests is like a recipe without taste-testing – you never know if it’s any good until it’s too late!”

Loading story...

Story - Premium Content

Please sign in to view this story and start learning.

Upgrade to Premium to unlock full access to all stories.

Stay Tuned!

Story is coming soon.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.