Learn / Frameworks / Spring Boot / Configuration, Profiles and Testing

Advanced 15 min

Configuration, Profiles and Testing

application.yml, profiles, and unit and integration tests.

What you will learn

  • Externalize configuration
  • Use profiles
  • Write MockMvc tests

The last pieces of a production-ready service: configuration that changes per environment, and automated tests that let you change code with confidence.

# application.yml
server:
  port: 8080
app:
  greeting: Hello
  max-tasks: 100
import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "app")
public record AppProperties(String greeting, int maxTasks) {}

// enable with @ConfigurationPropertiesScan on the application class
// inject: public HelloController(AppProperties props) { ... }

Spring reads properties from many places, later ones overriding earlier ones: the file, environment variables (APP_MAXTASKS=50), command-line arguments. This is how the same jar runs in every environment. Keep secrets in environment variables, never in git.

Profiles

Profiles switch configuration and even beans per environment.

# application-dev.yml
spring:
  datasource:
    url: jdbc:h2:mem:devdb

# application-prod.yml
spring:
  datasource:
    url: jdbc:postgresql://db:5432/app
java -jar app.jar --spring.profiles.active=prod
# or: SPRING_PROFILES_ACTIVE=prod

Unit tests

Test services in isolation with plain JUnit and Mockito. They start instantly because no Spring container is needed.

import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;

@ExtendWith(MockitoExtension.class)
class TaskServiceTest {

    @Mock TaskRepository repo;
    @InjectMocks TaskService service;

    @Test
    void getThrowsWhenMissing() {
        when(repo.findById(1L)).thenReturn(java.util.Optional.empty());
        assertThrows(TaskNotFoundException.class, () -> service.get(1L));
    }
}
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@WebMvcTest(TaskController.class)
class TaskControllerTest {

    @Autowired MockMvc mvc;
    @MockBean TaskService service;

    @Test
    void rejectsBlankTitle() throws Exception {
        mvc.perform(post("/api/tasks")
                .contentType("application/json")
                .content("{\"title\": \"\"}"))
           .andExpect(status().isBadRequest());
    }

    @Test
    void returnsTask() throws Exception {
        when(service.findById(1L)).thenReturn(new TaskResponse(1L, "Write tests", false));
        mvc.perform(get("/api/tasks/1"))
           .andExpect(status().isOk())
           .andExpect(jsonPath("$.title").value("Write tests"));
    }
}

@WebMvcTest loads only the web layer, so it stays fast. Use @SpringBootTest for full integration tests, ideally with Testcontainers to run a real PostgreSQL in Docker.

./mvnw clean package
java -jar target/demo-0.0.1-SNAPSHOT.jar

Boot builds a single executable jar containing the embedded server. It runs anywhere Java runs, and easily fits into a Docker image.

What next

Spring Security for authentication and authorization, Spring Cache, messaging with Kafka or RabbitMQ, and Micrometer with Actuator for observability.

Try it yourself

Write a MockMvc test asserting that GET /api/tasks/999 returns 404 when the service throws TaskNotFoundException.

Show solution
@Test
void missingTaskIs404() throws Exception {
    when(service.findById(999L)).thenThrow(new TaskNotFoundException(999L));
    mvc.perform(get("/api/tasks/999"))
       .andExpect(status().isNotFound());
}