Spark vs Testcontainers
Spark and Testcontainers share the same philosophy: test against real services in Docker, not mocks. The difference is the approach — Testcontainers is a code library, Spark is a standalone tool.
Quick comparison
| Spark | Testcontainers | |
|---|---|---|
| Approach | Standalone CLI + YAML | Library embedded in your test framework |
| Test format | YAML files | Code (Java, Go, Python, Node.js, etc.) |
| Language | Language-agnostic | Per-language (separate libraries) |
| Service management | Built-in (YAML) | Built-in (code) |
| Test isolation | Automatic per test | Automatic per test |
| White-box testing | No — HTTP/CLI only | Yes — full access to application internals |
| Parallel execution | Built-in | Framework-dependent |
| Distributed execution | Yes (cloud workers) | No |
| Module ecosystem | Service templates in spark.yaml | 50+ pre-built modules |
| Backed by | Finie | Docker Inc. |
When to use Testcontainers
- You need white-box testing — calling internal functions, checking database state directly
- Your team already writes tests in Java/Go/Python and wants containers alongside unit tests
- You need pre-built modules for complex services (Kafka, Elasticsearch, LocalStack, etc.)
- You want containers managed by the same test lifecycle as your unit tests
- You need Testcontainers Cloud for running containers without local Docker
When to use Spark
- Your team is polyglot — one test format for any tech stack
- You want non-developers (QA, DevOps) to write and maintain tests
- You need distributed execution across multiple workers
- You want tests as reviewable YAML in pull requests, not hidden in code
- You don't want test infrastructure coupled to your application language
The key difference
Testcontainers lives inside your code:
@Container
static PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:15");
@Test
void testUserCreation() {
var url = postgres.getJdbcUrl();
// ... connect, insert, assert
}
Spark lives outside your code:
name: User Creation
tests:
- name: Create user returns 201
services:
- name: db
image: postgres:15
healthcheck: "pg_isready"
- name: api
image: myapp:latest
execution:
target: http://api:8080
request:
method: POST
url: /api/users
body: '{"email": "test@test.com"}'
assertions:
- statusCode:
equals: 201
Neither is better. They test at different levels.
Where Testcontainers wins
- White-box testing — test internal methods, query databases directly, inspect application state. Spark can only test what is exposed via HTTP or CLI.
- Test framework integration — lifecycle hooks, dependency injection, test annotations. Tests feel native to your language.
- Module ecosystem — 50+ pre-built modules with sensible defaults for popular services. Spark requires you to write Docker configuration manually.
- Maturity — years of development, large community, backed by Docker Inc.
- Reusable containers — keep containers running across tests for speed (trade-off: less isolation)
Where Spark wins
- Language-agnostic — one YAML format regardless of whether your app is Java, Python, Go, or PHP. No Testcontainers library needed per language.
- Accessible to non-developers — QA engineers, DevOps, or product managers can read and write YAML tests without knowing Java or Go.
- Distributed execution — run tests across multiple workers for faster feedback. Testcontainers runs everything on one machine.
- Built-in assertions — HTTP status codes, JSON paths, snapshots — no need to bring your own assertion library.
- PR-friendly — YAML diffs are clear and reviewable. Code-based tests mix infrastructure setup with test logic.
- No compilation — run
spark run ./testswithout building anything. Testcontainers requires your full build toolchain.
Can they coexist?
Yes. Many teams use Testcontainers for unit-level integration tests (testing internal logic with real databases) and Spark for end-to-end integration tests (testing the full API from the outside). They complement each other.