Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save mauricioaniche/c6f80e32f79b55ed6fec439a2280395a to your computer and use it in GitHub Desktop.

Select an option

Save mauricioaniche/c6f80e32f79b55ed6fec439a2280395a to your computer and use it in GitHub Desktop.
## Spring Boot testing guidelines
When adding or changing tests in this Spring Boot application, prefer behavior-focused component tests over isolated unit tests. Tests should exercise the application the way production code is invoked, while keeping external dependencies controlled.
### Test from the system boundary
- Tests for request-driven behavior must start from a controller endpoint.
- Do not create isolated tests for services, entities, repositories, mappers, validators, or other classes that are reachable through an endpoint.
- A service class should be covered through controller tests that trigger the service through the HTTP/API boundary.
- Entity behavior, validation, persistence mappings, repository queries, and transactional behavior should be exercised as part of controller-driven tests whenever the behavior is reachable from an endpoint.
- Prefer `@SpringBootTest` with `MockMvc`. `MockMvc` is appropriate for Spring MVC tests because it performs Spring MVC request handling without requiring a running servlet container.
### Asynchronous jobs and non-controller entry points
- Asynchronous jobs, schedulers, consumers, command handlers, listeners, and batch jobs are not invoked by controllers, so they must have their own tests.
- These tests must start from the outermost application component that invokes the job logic.
- For a scheduled job, test from the scheduler/job class.
- For a queue/message consumer, test from the consumer/listener class.
- For a batch process, test from the batch/job launcher or equivalent entry point.
- Do not test only an internal helper service used by the job unless there is no outer application component available.
- The test should verify the externally observable behavior of the job: persisted data, emitted events, calls to external systems, state transitions, logs only when logs are the actual behavior, or other visible outcomes.
### Mocking policy
Avoid mocking by default.
Do not mock:
- Domain entities.
- Services.
- Repositories.
- Spring Data repositories.
- Query components.
- Mappers.
- Validators.
- Internal application collaborators.
- The database.
Mock or fake only dependencies that cross the system boundary, such as:
- HTTP clients for external APIs.
- SDK clients for third-party services.
- Email/SMS/push notification providers.
- Payment gateways.
- Cloud services.
- Message brokers when the broker itself is not the subject of the test.
- Time providers, ID generators, or random generators when determinism is needed.
Repositories and databases must not be mocked.
### Database and persistence testing
- Tests that depend on persistence must run against an in-memory database.
- Do not replace repository behavior with mocks.
- Verify that queries, constraints, relationships, transactions, and migrations behave correctly.
- Seed test data explicitly in the test or through clear test fixtures.
- Each test must be independent and must not rely on execution order.
- Clean up database state between tests using transactions, cleanup scripts, or isolated schemas/containers.
- Avoid excessive fixture setup. Create only the data required to express the behavior being tested.
### Test behavior, not implementation
Tests must assert observable behavior, not internal implementation details.
Prefer asserting:
- HTTP status codes.
- Response body fields relevant to the behavior.
- Persisted database state.
- Domain-visible state transitions.
- Side effects at system boundaries.
- Validation errors.
- Security/authorization behavior.
- Idempotency and retry behavior when relevant.
Avoid asserting:
- That a specific internal method was called.
- The exact sequence of internal service calls.
- Private method behavior.
- Internal object structure that is not part of the API contract.
- Implementation details that could change without changing user-visible behavior.
### Boundary and edge cases
When testing logic, include boundary tests. Cover at least:
- Minimum valid value.
- Maximum valid value.
- Just below the minimum.
- Just above the maximum.
- Empty input.
- Null or missing input, when applicable.
- Duplicate input, when applicable.
- Invalid enum/status values.
- Unauthorized and forbidden access.
- Not found cases.
- Conflict cases.
- Idempotent repeated requests, when applicable.
- Time boundaries, such as start/end of day, expiration time, and timezone-sensitive behavior.
- Numeric boundaries, such as zero, negative values, rounding, and precision.
### Controller test expectations
Controller-driven tests should usually verify the full request path:
1. Build a realistic request.
2. Send it through the controller boundary.
3. Assert the HTTP response.
4. Assert the database state or other observable side effects.
5. Assert external boundary interactions only when those interactions are part of the behavior.
### Exception handling and clean test execution
Tests must not pass while producing uncaught exceptions, stack traces, unexpected warnings, or noisy logs.
- Do not ignore exceptions thrown by background threads, async tasks, schedulers, or event listeners.
- Do not allow tests to pass only because an exception happens after the assertion.
- Configure async executors, schedulers, and listeners so failures are visible to the test.
- Await asynchronous behavior deterministically instead of using arbitrary sleeps.
- Fail the test when unexpected exceptions are logged or swallowed.
- Do not suppress errors just to make the test pass.
- Test execution should be clean and deterministic.
### External systems
For external HTTP calls or third-party integrations:
- If the logic isn't isolated in an easy-to-mock class, extract all the logic to a class so that it gets easily mockable.
- Mock at the boundary, not inside the domain.
- Assert that the application handles success, failure, timeout, malformed responses, and retryable errors.
- Do not make real network calls in automated tests.
- Keep external API fixtures small and explicit.
### Test quality rules
Every test should have a clear behavior-oriented name.
Prefer names like:
```java
shouldCreateOrderWhenRequestIsValid()
shouldReturnBadRequestWhenEmailIsMissing()
shouldNotChargeCustomerTwiceWhenRequestIsRetried()
shouldExpireInvitationAtExpirationTime()
```
Avoid names like:
```
testCreate()
testService()
shouldCallRepository()
```
Tests should be deterministic:
- No dependency on current time without controlling the clock.
- No dependency on random values without fixed seeds or injectable generators.
- No dependency on test execution order.
- No arbitrary Thread.sleep.
- No shared mutable state across tests.
- What not to generate
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment