Spring Boot Profiles + Testing Basics 

  1. What are Spring profiles?
  2. How to activate a profile?
  3. @Profile annotation
  4. application.properties vs application-dev.properties
  5. YAML profile support
  6. @ActiveProfiles in tests
  7. What is @SpringBootTest?
  8. @WebMvcTest vs @DataJpaTest vs @SpringBootTest
  9. Mockito basics
  10. @MockBean vs @Mock
  11. How to test REST controllers?
  12. Test JPA repositories
  13. Mock external services
  14. AssertJ vs JUnit assertions
  15. Best practices for Spring Boot Profiles + Testing Basics

What are Spring profiles? 

=> Spring Profiles allow environment-specific configurations (dev, test, prod) via profile-specific files like application-dev.properties

1. Profile-Specific Files

=> application.properties — default (always loaded)

=> application-dev.properties — for dev profile

=> application-prod.properties — for prod profile

=> application-test.yml — YAML also supported

Example : 

# application.properties (default)
server.port=8080

# application-dev.properties
server.port=8081
spring.datasource.url=jdbc:h2:mem:devdb

# application-prod.properties
server.port=80
spring.datasource.url=jdbc:postgresql://prod-db:5432/appdb

2. Activating a Profile

Command line (most common)

java -jar app.jar --spring.profiles.active=dev

application.properties

spring.profiles.active=dev

Environment variable

export SPRING_PROFILES_ACTIVE=prod

In tests

@SpringBootTest
@ActiveProfiles("test")
class MyTest { ... }

3. @Profile Annotation

Beans or methods active only in specific profiles

@Configuration
@Profile("dev")
public class DevConfig {
    @Bean
    public DataSource devDataSource() {
        return new H2DataSource();
    }
}

4. Multiple Profiles

--spring.profiles.active=dev,featureX

=> Loads application-dev.properties + application-featureX.properties

5. YAML Profiles

# application.yml
spring:
  profiles:
    active: dev

---
spring:
  profiles: dev
server:
  port: 8081 

Best Practice 

=> Default (application.properties) for common settings
=> Profile-specific files for overrides
=> Use in tests with @ActiveProfiles("test")
=> Production: Activate via env variables or Kubernetes ConfigMaps

How to activate a profile?

1. Command Line Argument (Most common in production/dev)

Bash 

java -jar your-app.jar --spring.profiles.active=dev,test 

2. In application.properties / application.yml (Good for local dev)

properties

spring.profiles.active=dev

3. Environment Variable (Best for Docker/Kubernetes)

Bash

export SPRING_PROFILES_ACTIVE=prod
java -jar your-app.jar 

or in Docker:

YAML

environment:
  - SPRING_PROFILES_ACTIVE=prod

4. Programmatically (Rare – in main method)

Java

SpringApplication.setAdditionalProfiles("dev");
SpringApplication.run(MyApp.class, args);

5. In Tests (Very common)

Java

@SpringBootTest
@ActiveProfiles("test")
class MyTest { ... }

Summary

=> Profiles are activated via command line (--spring.profiles.active=dev), application.properties (spring.profiles.active=dev), environment variable (SPRING_PROFILES_ACTIVE), or @ActiveProfiles in tests. 
=> Command line has highest priority.

@Profile annotation

=> @Profile is a Spring annotation that conditionally includes or excludes beans, configurations, or components based on the active profile(s) at runtime

1. On a Bean / Configuration Class

@Configuration
@Profile("dev")
public class DevConfig {
    @Bean
    public DataSource devDataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)
            .build();
    }
}

=> This DataSource is created only when dev profile is active

2. On Individual Bean Methods

@Bean
@Profile("prod")
public DataSource prodDataSource() {
    return new DataSourceBuilder()
        .url("jdbc:postgresql://prod-db:5432/app")
        .build();
}

3. On Component / Service / Controller

@Service
@Profile("test")
public class TestDataService {
    // Only active in "test" profile
}

Multiple Profiles

@Profile({"dev", "local"})  // Active if either dev or local is active

Negation (Exclude Profile)

@Profile("!prod")  // Active when prod is NOT active 

application.properties vs application-dev.properties

=> application.properties is the default config loaded always.
=> application-dev.properties is profile-specific, loaded only when spring.profiles.active=dev, and overrides defaults. This allows environment-specific settings (e.g., H2 for dev, PostgreSQL for prod) without code changes

YAML profile support

=> We can use either application.properties or application.yml file for Spring Profiles 
=> application.yml is more structured and readable when compared to application.properties

@ActiveProfiles in tests  

=> @ActiveProfiles is a Spring Boot annotation used in test classes to specify which profile(s) should be activated during the test execution
=> It is used with @SpringBootTest 
=> It tells Spring to load the environment-specific configuration for that test
=> Multiple profiles can be specified as array 
=> Simulate different environments (test, dev, prod) in unit/integration tests
=> Load correct database, ports, logging levels, or beans for the test
=> Avoid using production config in tests (e.g., real DB vs in-memory H2)

1. Basic Example (Single Profile)

@SpringBootTest
@ActiveProfiles("test")  // Loads application-test.properties or application-test.yml
class EmployeeServiceTest {
    @Autowired
    private EmployeeService service;

    @Test
    void testCreateEmployee() {
        // Test runs with test profile config
    }
}

2. Multiple Profiles

@SpringBootTest
@ActiveProfiles({"test", "integration"})
class IntegrationTest { ... }

=> Loads application-test.properties + application-integration.properties (last one wins for conflicts).

3. On Test Class or Method (Method-level overrides class-level)

@SpringBootTest
@ActiveProfiles("test")
class MyTests {
    @Test
    @ActiveProfiles("prod")  // Overrides class-level for this test only
    void prodSpecificTest() { ... }
}

What is @SpringBootTest?

=> @SpringBootTest loads the full Spring Boot application context for integration tests, including auto-configuration, beans, and properties
=> Starts the full context (like running the app) but in test mode (uses test properties if present)
=> Useful for end-to-end testing
=> Customize with classes, webEnvironment, properties, or @ActiveProfiles
=> It's heavier than @WebMvcTest or @DataJpaTest, so use only when full context is needed

Common Attributes

AttributeDefaultDescription
classesMain classSpecify main class (e.g., @SpringBootTest(classes = DemoApplication.class))
webEnvironmentMOCKMOCK: Mock servlet environment RANDOM_PORT: Real server on random port DEFINED_PORT: Real server on defined port NONE: No web environment
propertiesNoneOverride properties for the test (e.g., properties = {"spring.datasource.url=jdbc:h2:mem:testdb"})
@ActiveProfilesNoneActivate specific profiles (e.g., @ActiveProfiles("test"))

When to Use
=> Integration tests (full context: controllers, services, repositories, DB)
=> Testing auto-configuration, beans, properties
=> End-to-end flow (controller → service → repository)

When NOT to Use (Lighter Alternatives)
=> @WebMvcTest: Only web layer (controllers, no DB)
=> @DataJpaTest: Only JPA layer (repositories, no web)
=> @SpringBootTest is heavy — use only when you need the full context

@WebMvcTest vs @DataJpaTest vs @SpringBootTest

=> @WebMvcTest: Only web layer (controllers, no DB)
=> @DataJpaTest: Only JPA layer (repositories, no web)
=> @SpringBootTest is heavy — use only when you need the full context

AnnotationWhat it loadsScope / Loaded ComponentsBest ForSpeed (Relative)When to Use
@WebMvcTestOnly web layer (controllers, filters, message converters, etc.)@Controller, @RestController, WebMvcConfigurer, HandlerMappings, etc.Testing controllers (REST endpoints)FastestUnit test controllers with MockMvc, mock services/repositories
@DataJpaTestOnly JPA layer (repositories, entities, DataSource, EntityManager)@Entity, JpaRepository, DataSource, Hibernate, etc.Testing repositories and JPA queriesVery FastUnit/integration test repositories, custom queries, JPA behavior
@SpringBootTestFull application context (everything: controllers, services, repositories, config, etc.)Entire Spring Boot app (like running the real app)Full integration/end-to-end testsSlowestWhen you need the complete app (e.g., full flow, multiple layers)

Quick Summary Table

Feature@WebMvcTest@DataJpaTest@SpringBootTest
Loads full context?NoNoYes
Loads controllers?YesNoYes
Loads repositories/JPA?NoYesYes
Loads services?NoNoYes
MockMvc available?YesNoYes (with webEnvironment)
Use @MockBean?Yes (for services)Yes (for external services)Yes
Best forController unit testsRepository/JPA unit testsFull integration tests
SpeedFastFastSlow

Examples : 

@WebMvcTest (Controller only) 

@WebMvcTest(HelloController.class)
class HelloControllerTest {
    @Autowired private MockMvc mockMvc;
    @MockBean private HelloService service;

    @Test
    void testHello() throws Exception {
        mockMvc.perform(get("/hello"))
               .andExpect(status().isOk())
               .andExpect(content().string("Hello"));
    }
}

@DataJpaTest (JPA only) 

@DataJpaTest
class EmployeeRepositoryTest {
    @Autowired private EmployeeRepository repo;

    @Test
    void testFindByName() {
        Employee emp = new Employee("Virat");
        repo.save(emp);
        assertNotNull(repo.findByName("Virat"));
    }
}

@SpringBootTest (Full app)

@SpringBootTest
class FullIntegrationTest {
    @Autowired private EmployeeController controller;

    @Test
    void testFullFlow() {
        // Test from controller to DB
    }
}

Mockito basics

 => Mockito is the most popular Java mocking framework used for unit testing in Spring Boot applications
=> It allows you to create mock objects (fake versions of dependencies) to isolate the class under test from external dependencies (services, repositories, external APIs)
=> @Mock – Fake object that simulates behavior of a real dependency
=> @InjectMocks – Automatically inject mocks into the class under test 

Step-by-Step Basics (with Code)

import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;

@ExtendWith(MockitoExtension.class)  // Enables Mockito annotations
class EmployeeServiceTest {

    @Mock  // Create mock dependency
    private EmployeeRepository repository;

    @InjectMocks  // Inject mock into the class under test
    private EmployeeService service;

    @Test
    void testFindEmployeeById() {
        // Stub: Define what mock returns
        Employee mockEmployee = new Employee(1, "Sachin", 800000);
        when(repository.findById(1L)).thenReturn(Optional.of(mockEmployee));

        // Call the method under test
        Employee result = service.findById(1L);

        // Assert
        assertEquals("Sachin", result.getName());

        // Verify: Check if repository method was called
        verify(repository, times(1)).findById(1L);
    }

    @Test
    void testSaveEmployee() {
        Employee emp = new Employee(null, "Virat", 500000);

        // Stub save to return saved employee with ID
        when(repository.save(any(Employee.class))).thenAnswer(invocation -> {
            Employee e = invocation.getArgument(0);
            e.setId(2L);  // Simulate DB generated ID
            return e;
        });

        Employee saved = service.save(emp);

        assertEquals(2L, saved.getId());
        verify(repository, times(1)).save(emp);
    }

Key Mockito Methods (Most Used)

MethodPurposeExample
when(mock.method()).thenReturn(value)Stub method return valuewhen(repo.findById(1L)).thenReturn(emp)
when(mock.method()).thenThrow(ex)Stub method to throw exceptionwhen(service.callApi()).thenThrow(new RuntimeException())
verify(mock, times(n)).method()Verify method called exactly n timesverify(repo, times(1)).save(emp)
verify(mock).method(arg)Verify called with specific argumentverify(repo).findByName("Virat")
any() / any(Class)Match any argumentwhen(repo.save(any())).thenReturn(emp)
@InjectMocksAuto-injects mocks into tested class@InjectMocks private EmployeeService service;
@MockCreate mock object@Mock private EmployeeRepository repo;

Best Practices
=> Use @ExtendWith(MockitoExtension.class) for JUnit 5
=> Use @MockBean in @SpringBootTest to replace real beans 

@MockBean vs @Mock

=> @Mock is plain Mockito for creating mocks in unit tests (no Spring context)
=> @MockBean is Spring Boot's annotation that replaces a real bean with a mock in the application context — used in integration tests with @SpringBootTest or @WebMvcTest

When to Choose Which

=> Use @Mock → For pure unit tests where you don't load Spring context (faster, no overhead)
=> Use @MockBean → For integration tests where you need the real Spring context but want to mock a specific bean (e.g., mock external service or repository)

Example : 

@Mock (Unit Test – No Spring)

@ExtendWith(MockitoExtension.class)
class EmployeeServiceTest {
    @Mock private EmployeeRepository repo;
    @InjectMocks private EmployeeService service;

    @Test
    void testFindById() {
        when(repo.findById(1L)).thenReturn(Optional.of(new Employee()));
        assertNotNull(service.findById(1L));
    }
}

@MockBean (Integration Test – With Spring)

@SpringBootTest
class EmployeeControllerTest {
    @Autowired private MockMvc mockMvc;
    @MockBean private EmployeeService service;  // Real service replaced with mock

    @Test
    void testGetEmployee() throws Exception {
        when(service.findById(1L)).thenReturn(new Employee());
        mockMvc.perform(get("/employees/1"))
               .andExpect(status().isOk());
    }

How to test REST controllers?

=> @WebMvcTest: Only web layer (controllers, no DB)
=> @WebMvcTest is used for testing Rest controllers effectively

Step-by-Step Guide 

1. Add Dependency (already included in spring-boot-starter-test) in pom.xml 

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

2. Basic Controller Example (to test)

@RestController
@RequestMapping("/employees")
public class EmployeeController {
    @Autowired
    private EmployeeService service;

    @GetMapping("/{id}")
    public Employee getEmployee(@PathVariable Long id) {
        return service.findById(id);
    }
}

3. Test Class with @WebMvcTest

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@WebMvcTest(EmployeeController.class)
class EmployeeControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private EmployeeService service;  // Mock the service

    @Test
    void testGetEmployee_Success() throws Exception {
        Employee emp = new Employee(1L, "Sachin", 800000);
        when(service.findById(1L)).thenReturn(emp);

        mockMvc.perform(get("/employees/1")
                .accept(MediaType.APPLICATION_JSON))
               .andExpect(status().isOk())
               .andExpect(jsonPath("$.name").value("Sachin"))
               .andExpect(jsonPath("$.salary").value(800000));

        verify(service, times(1)).findById(1L);
    }

    @Test
    void testGetEmployee_NotFound() throws Exception {
        when(service.findById(999L)).thenThrow(new ResourceNotFoundException("Not found"));

        mockMvc.perform(get("/employees/999"))
               .andExpect(status().isNotFound());
    }
}

Best Practice 
=> Use @WebMvcTest for controller tests (fast, isolated)
=> Mock services/repositories with @MockBean
=> Use MockMvc for HTTP simulation (no real server)
=> Test status codes, JSON content, exceptions


Test JPA repositories

=>  Use @DataJPATest that loads only the JPA layer (entities, repositories, DataSource, Hibernate)

AssertJ vs JUnit assertions?

=> JUnit assertions are static and basic (assertEquals, assertTrue). 
=> AssertJ is fluent and chainable (assertThat(list).hasSize(5).contains("x")), with detailed error messages and soft assertions
=> AssertJ is preferred in modern Spring Boot projects for readability and power, while JUnit is simpler and sufficient for basic tests 

Quick Example Comparison

JUnit (classic)

@Test
void testList() {
    List<String> list = List.of("A", "B", "C");
    assertEquals(3, list.size());
    assertTrue(list.contains("B"));
    assertEquals("A", list.get(0));
}

AssertJ (fluent & readable)

@Test
void testList() {
    List<String> list = List.of("A", "B", "C");
    assertThat(list)
        .hasSize(3)
        .contains("B")
        .startsWith("A")
        .doesNotContain("Z");

Best practices for Spring Boot Profiles + Testing Basics

=>Profiles: Use application-{profile}.properties for overrides, activate via command line/env variable, @Profile for beans, @ActiveProfiles in tests. 

=> Testing: Choose @WebMvcTest/@DataJpaTest for fast isolated tests, @SpringBootTest for full integration, mock dependencies with @MockBean, use AssertJ for readable assertions, test validation and exceptions, keep tests fast with in-memory DB