Chapter 1: Introduction to Testing
Introduction
Before writing your first test, it is crucial to understand why and how to test effectively. This chapter lays the foundation for a professional testing strategy.
🎯 Chapter Objectives
By the end of this chapter, you will know:
- ✅ Why tests are essential
- ✅ Distinguish different types of tests
- ✅ Understand the testing pyramid
- ✅ Set up your project for testing
- ✅ Know best practices
1. Why Test?
The Cost of Bugs
| Detection Moment | Relative Cost | Impact |
|---|---|---|
| During development | 1x | None |
| In testing phase | 10x | Delay |
| In production | 100x | Client impacted, urgency |
Benefits of Testing
✅ Confidence: Code does what it's supposed to
✅ Regression: Detects bugs introduced by changes
✅ Documentation: Tests explain expected behavior
✅ Design: Writing tests enforces good design (SOLID)
✅ Refactoring: Modify code without fear
✅ Deployment: Automated CI/CD
Without Tests vs With Tests
// Without tests 😰
public void deployToProduction() {
// "I hope it works..."
// "We'll see in prod..."
// "Fingers crossed..."
}
// With tests 😎
public void deployToProduction() {
// ✅ 347 tests passed
// ✅ Coverage: 85%
// ✅ All quality gates passed
// → Deployment with confidence!
}
2. Types of Tests
Overview
Unit Tests
What: Test a unit of code in isolation (method, class)
Characteristics:
- Fast (milliseconds)
- Isolated (no DB, no network)
- Numerous (majority of tests)
@Test
void shouldCalculatePriceWithDiscount() {
// Test ONLY the calculation logic
PriceCalculator calculator = new PriceCalculator();
double result = calculator.applyDiscount(100.0, 20);
assertEquals(80.0, result);
}
Integration Tests
What: Test multiple components together
Characteristics:
- Slower (seconds)
- Use real resources (DB, etc.)
- Less numerous
@SpringBootTest
class OrderServiceIntegrationTest {
@Autowired
private OrderService orderService;
@Test
void shouldSaveOrderToDatabase() {
// Test service + repository + DB
Order order = orderService.createOrder(/*...*/);
assertNotNull(order.getId());
}
}
End-to-End (E2E) Tests
What: Test the complete application as a user
Characteristics:
- Slow (minutes)
- Complete environment
- Few in number
// With Selenium for example
@Test
void userShouldBeAbleToCompleteCheckout() {
browser.navigate("/products");
browser.click("#add-to-cart");
browser.click("#checkout");
browser.fill("#address", "123 Main St");
browser.click("#confirm");
assertThat(browser.getText("#confirmation"))
.contains("Order confirmed");
}
3. The Testing Pyramid
Ideal Structure
/\
/ \
/ E2E\ Few, slow, fragile
/ \
/────────\
/Integration\ Moderate
/ \
/────────────────\
/ Unit Tests \ Many, fast, stable
/____________________\
Recommended Proportions
| Type | Proportion | Execution Time |
|---|---|---|
| Unit | 70% | ~5 seconds |
| Integration | 20% | ~30 seconds |
| E2E | 10% | ~5 minutes |
Anti-Pattern: The Ice Cream Cone
____________________
/ Many E2E Tests \ ❌ Slow, fragile
/────────────────────────\
/ Few integration tests \
/────────────────────────────\
/ Few unit tests \
/ unit tests \
/──────────────────────────────────\
❌ BAD: Slow tests, difficult maintenance
4. TDD and BDD
TDD (Test-Driven Development)
Write tests BEFORE the code:
// Step 1: RED - Test fails (method doesn't exist)
@Test
void shouldReturnEmptyListWhenNoProducts() {
ProductService service = new ProductService();
List<Product> products = service.findAll();
assertTrue(products.isEmpty());
}
// Step 2: GREEN - Implement the minimum
public List<Product> findAll() {
return new ArrayList<>();
}
// Step 3: REFACTOR - Improve if necessary
BDD (Behavior-Driven Development)
Tests that describe behavior in natural language:
// Format Given-When-Then
@Test
void givenNewUser_whenRegister_thenAccountIsCreated() {
// Given (Context)
RegistrationRequest request = new RegistrationRequest(
"john", "john@email.com", "password123"
);
// When (Action)
User user = userService.register(request);
// Then (Expected result)
assertNotNull(user.getId());
assertEquals("john", user.getUsername());
assertTrue(user.isEnabled());
}
5. Maven Configuration
Test Dependencies
<!-- pom.xml -->
<dependencies>
<!-- Spring Boot Test (includes JUnit 5, Mockito, AssertJ) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- H2 for tests -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<!-- Security Test (optional) -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
What's included in spring-boot-starter-test
spring-boot-starter-test includes:
├── JUnit 5 (Jupiter)
├── Mockito
├── AssertJ
├── Hamcrest
├── JSONPath
├── JsonAssert
└── Spring Test
Test File Structure
src/
├── main/
│ └── java/
│ └── com/example/
│ ├── service/
│ │ └── ProductService.java
│ └── repository/
│ └── ProductRepository.java
└── test/
└── java/
└── com/example/
├── service/
│ └── ProductServiceTest.java ← Unit tests
├── repository/
│ └── ProductRepositoryTest.java ← Integration tests
└── controller/
└── ProductControllerTest.java ← API tests
6. Test Configuration
application-test.properties
# src/test/resources/application-test.properties
# In-memory H2 database
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driver-class-name=org.h2.Driver
spring.jpa.hibernate.ddl-auto=create-drop
# Disable verbose logs
logging.level.org.hibernate.SQL=WARN
spring.jpa.show-sql=false
# Disable Flyway for tests (optional)
spring.flyway.enabled=false
Activate Test Profile
@SpringBootTest
@ActiveProfiles("test") // ← Uses application-test.properties
class MyIntegrationTest {
// ...
}
7. Running Tests
Via Maven
# All tests
mvn test
# Tests with report
mvn test jacoco:report
# A specific test
mvn test -Dtest=ProductServiceTest
# A specific method
mvn test -Dtest=ProductServiceTest#shouldFindProductById
# Skip tests (to avoid!)
mvn install -DskipTests
Via IDE
IntelliJ IDEA:
- Right-click on class/method → Run Test
- Ctrl+Shift+F10 (Windows)
- Ctrl+Shift+R (Mac)
VS Code:
- "Run Test" icon above @Test
- Test Explorer in the sidebar
8. Best Practices
Naming Convention
// Recommended format: should_ExpectedBehavior_When_StateUnderTest
@Test
void should_ReturnEmpty_When_NoProductsExist() { }
// Or: given_When_Then
@Test
void given_ValidProduct_When_Save_Then_IdIsGenerated() { }
// Avoid:
@Test
void test1() { } // ❌ Non-descriptive
@Test
void testProductService() { } // ❌ Too vague
AAA Pattern (Arrange-Act-Assert)
@Test
void shouldCalculateTotalPrice() {
// Arrange (Prepare)
ShoppingCart cart = new ShoppingCart();
cart.addItem(new Item("Laptop", 1000.0));
cart.addItem(new Item("Mouse", 50.0));
// Act (Execute)
double total = cart.calculateTotal();
// Assert (Verify)
assertEquals(1050.0, total);
}
One Test = One Concept
// ❌ BAD: Tests multiple things
@Test
void testProduct() {
Product p = new Product("Test", 100.0);
assertEquals("Test", p.getName());
assertEquals(100.0, p.getPrice());
p.applyDiscount(10);
assertEquals(90.0, p.getPrice());
assertThrows(Exception.class, () -> p.applyDiscount(-5));
}
// ✅ GOOD: One test per behavior
@Test
void shouldCreateProductWithNameAndPrice() {
Product p = new Product("Test", 100.0);
assertEquals("Test", p.getName());
assertEquals(100.0, p.getPrice());
}
@Test
void shouldApplyDiscountCorrectly() {
Product p = new Product("Test", 100.0);
p.applyDiscount(10);
assertEquals(90.0, p.getPrice());
}
@Test
void shouldRejectNegativeDiscount() {
Product p = new Product("Test", 100.0);
assertThrows(IllegalArgumentException.class,
() -> p.applyDiscount(-5));
}
9. Summary
Key Points
| Concept | Description |
|---|---|
| Unit Test | Tests a unit in isolation |
| Integration Test | Tests multiple components together |
| E2E Test | Tests the complete application |
| TDD | Tests written before the code |
| AAA | Arrange-Act-Assert |
Testing Pyramid
10% E2E (slow, fragile)
20% Integration (moderate)
70% Unit (fast, numerous)
Checklist
✅ spring-boot-starter-test in pom.xml
✅ application-test.properties configured
✅ Tests in src/test/java
✅ Descriptive naming
✅ One test = one concept
✅ AAA Pattern
🎯 Next Step
Time for practice! Let's explore JUnit 5 in detail.
➡️ Chapter 2: Unit Testing with JUnit 5