Practical Test Layering Guide | Generated by AI

Home 2025.09

Great question. A sane test strategy is mainly about risk and feedback speed. Here’s a practical way to decide what to test at which layer—no tables, just clear rules and examples.


Core principles


What goes where (quick heuristics)

1) Unit tests (fast, isolated)

Use when: Pure/domain logic can be tested without I/O (DB, HTTP, filesystem).

Java/Spring example

@ExtendWith(MockitoExtension.class)
class FeeServiceTest {
  @Mock AccountRepo repo;
  @InjectMocks FeeService svc;

  @Test void vipGetsDiscount() {
    when(repo.tier("u1")).thenReturn("VIP");
    assertEquals(Money.of(90), svc.charge("u1", Money.of(100)));
    verify(repo).tier("u1");
  }
}

2) Integration / component tests (real wiring, minimal mocks)

Use when: You need to verify Spring wiring, serialization, filters, DB queries, transactions.

HTTP slice example

@WebMvcTest(controllers = OrderController.class)
class OrderControllerTest {
  @Autowired MockMvc mvc;
  @MockBean OrderService svc;

  @Test void createsOrder() throws Exception {
    when(svc.create(any())).thenReturn(new Order("id1", 100));
    mvc.perform(post("/orders").contentType("application/json")
        .content("{\"amount\":100}"))
      .andExpect(status().isCreated())
      .andExpect(jsonPath("$.id").value("id1"));
  }
}

DB with Testcontainers

@Testcontainers
@SpringBootTest
class RepoIT {
  @Container static PostgreSQLContainer<?> db = new PostgreSQLContainer<>("postgres:16");
  @Autowired OrderRepo repo;

  @Test void persistsAndQueries() {
    var saved = repo.save(new OrderEntity(null, 100));
    assertTrue(repo.findById(saved.getId()).isPresent());
  }
}

3) API contract & end-to-end API tests

Use when: You must guarantee backward-compatible contracts or full system workflows.

API E2E example

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class ApiFlowIT {
  @LocalServerPort int port;
  @Test void happyPath() {
    given().port(port).contentType("application/json")
      .body("{\"amount\":100}")
      .when().post("/orders")
      .then().statusCode(201)
      .body("amount", equalTo(100));
  }
}

4) UI end-to-end tests (browser)

Use when: Only a few critical user journeys must be proven in a real browser:

Selenium vs. Playwright/Cypress?

Playwright (TypeScript) example

import { test, expect } from '@playwright/test';

test('checkout happy path', async ({ page }) => {
  await page.goto('http://localhost:4200');
  await page.getByRole('button', { name: 'Sign in' }).click();
  await page.getByLabel('Email').fill('u@example.com');
  await page.getByLabel('Password').fill('secret');
  await page.getByRole('button', { name: 'Login' }).click();

  await page.getByText('Add to cart', { exact: true }).first().click();
  await page.getByRole('button', { name: 'Checkout' }).click();
  await expect(page.getByText('Order confirmed')).toBeVisible();
});

If you must use Selenium (Java)

WebDriver d = new ChromeDriver();
d.get("http://localhost:4200");
new WebDriverWait(d, Duration.ofSeconds(10))
  .until(ExpectedConditions.elementToBeClickable(By.id("loginBtn"))).click();

Deciding layer-by-layer (quick flow)

  1. Can it be tested without I/O? → Yes: Unit test it.

  2. Does it depend on framework wiring/serialization or DB queries? → Yes: Integration/component test (Spring slices, Testcontainers).

  3. Is it a cross-service/public API contract? → Yes: Contract tests (schema/Pact) + a couple API E2E flows.

  4. Is the value only visible in the UI or critical UX? → Yes: UI E2E, but only core journeys.


Sensible proportions & budgets


What to prioritize (risk-based checklist)


CI pipeline shape (pragmatic)


Test data & flakiness tips


TL;DR rules of thumb

If you want, tell me your top 3–5 business-critical flows and I’ll sketch the exact test list per layer (with tool choices and CI gating).


Back Donate