Skip to main content

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:

  1. ✅ Why tests are essential
  2. ✅ Distinguish different types of tests
  3. ✅ Understand the testing pyramid
  4. ✅ Set up your project for testing
  5. ✅ Know best practices

1. Why Test?​

The Cost of Bugs​

Detection MomentRelative CostImpact
During development1xNone
In testing phase10xDelay
In production100xClient 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
/____________________\
TypeProportionExecution Time
Unit70%~5 seconds
Integration20%~30 seconds
E2E10%~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​

ConceptDescription
Unit TestTests a unit in isolation
Integration TestTests multiple components together
E2E TestTests the complete application
TDDTests written before the code
AAAArrange-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


⬅️ Back to Module 9