JUnit & TestNG
The foundations of Java testing. JUnit for standard unit tests, TestNG for complex integration and data-driven testing.
Overview
JUnit is the standard unit testing framework for Java, first created by Kent Beck and Erich Gamma in 1997. JUnit 5 (released in 2017) modernised the framework with a modular architecture, lambda-based assertions, and powerful extension mechanisms. TestNG, created by Cédric Beust in 2004, was designed to address JUnit 3/4 limitations and excels at integration testing, parallel execution, and data-driven testing.
Most NZ Java projects use JUnit 5 for unit tests and may use TestNG for complex integration scenarios. Both integrate seamlessly with Maven, Gradle, Spring Boot, and all Java CI/CD tools.
What it's used for
JUnit/TestNG are essential for:
- Java unit testing: The standard frameworks for testing Java code.
- Spring Boot testing: @SpringBootTest, @WebMvcTest, and @DataJpaTest annotations.
- Integration testing: Test database layers, service layers, and API controllers.
- Data-driven testing: TestNG's @DataProvider and JUnit 5's @ParameterizedTest.
Pros & Cons
Pros
- Industry standard for Java testing
- Excellent Spring Boot integration
- Rich assertion libraries (AssertJ, Hamcrest)
- Strong IDE support (IntelliJ, Eclipse, VS Code)
- Extensive documentation and community resources
Cons
- Java only — not suitable for other languages
- JUnit 5 migration from JUnit 4 requires effort
- TestNG has a smaller community than JUnit
- Configuration can be verbose for complex setups
- Parallel execution requires careful resource management
Platforms & Integrations
JUnit and TestNG run on Windows, macOS, and Linux via the JVM. They integrate with Maven, Gradle, and all Java CI/CD tools.
Pricing
| Tier | Cost | Includes |
|---|---|---|
| Open Source | Free | Full framework, all features, community support |
NZ Context
JUnit is the default testing framework in NZ Java teams. Every Java developer job posting in NZ lists JUnit experience. TestNG appears less frequently but is common in test automation roles. NZ banks and financial services organisations, NZ government agencies, and large NZ insurers all rely heavily on JUnit for their Java backend systems — years of investment in these suites means the skill remains consistently in demand. For NZ testers working in Java environments, JUnit 5 proficiency is mandatory.
Alternatives
- Spock — Groovy-based testing with expressive syntax. Popular in Gradle projects.
- AssertJ — Fluent assertion library that pairs with JUnit for readable tests.
- Cucumber-JVM — BDD framework for Java using Gherkin syntax.
When to choose JUnit / TestNG
A quick decision guide for NZ teams evaluating Java unit testing options.
| Choose JUnit / TestNG when… | Choose something else when… | Combine with… |
|---|---|---|
| Your codebase is Java or Kotlin, and the team already uses Spring Boot or Maven/Gradle | The team writes primarily in Python, JavaScript, or Go — use pytest, Jest, or the Go testing package instead; forcing Java tooling adds unnecessary overhead | Mockito for mocking dependencies; AssertJ for fluent, readable assertions; WireMock for stubbing external HTTP services |
| You need data-driven tests with multiple input sets — TestNG's @DataProvider handles this cleanly without third-party libraries | You want business-readable test scenarios for non-technical stakeholders — Cucumber-JVM with Gherkin is a better fit for BDD workflows | JaCoCo for code coverage reporting; Surefire/Failsafe Maven plugins to separate unit and integration test phases in CI |
| You need parallel test execution across a large integration suite — TestNG's XML suite configuration and thread-pool control is purpose-built for this | Your team is Groovy-heavy or your build tool is Gradle — Spock provides more expressive given/when/then blocks and integrates natively | Testcontainers for spinning up real databases and message brokers in integration tests; Allure for structured HTML reports from either framework |
| The project is a Spring Boot microservice — JUnit 5 + @SpringBootTest is the ecosystem default and every Spring tutorial assumes it | You are testing REST APIs as a black-box tester without Java code access — REST-assured or Postman/Newman are far more accessible | ArchUnit for enforcing architectural rules (e.g. no service layer touching the web layer directly); H2 in-memory database for fast repository tests |
What I would do
Practitioner judgment on tool adoption, team onboarding, and when to swap.
junit-vintage-engine dependency so JUnit 4 tests keep running, then convert one module at a time. Introduce AssertJ as the assertion library in the same pass; its compile-time type safety catches bugs the old string-based assertions miss. Raise a tech-debt story for each module, not a single big-bang migration — CloudBooks's release cadence is too fast to freeze the suite for weeks.testng.xml suite file — aim for 4 threads to start, verify no shared-state failures, then increase. Pair this with Testcontainers so each test class gets an isolated Oracle-compatible H2 instance rather than fighting over a shared schema. In my experience on government Java systems, this combination cuts integration test time by 60–70% without touching the test logic itself.The bottom line: Default to JUnit 5 on every new Java project — it is the ecosystem standard and your team will find answers faster. Reach for TestNG when you have a concrete parallelism or dependency-ordering problem that JUnit's extension model can't solve without significant custom code.
Interview questions
Questions you are likely to get if you list JUnit / TestNG on your CV — with what interviewers are really testing for.
What is the difference between JUnit 5 and TestNG, and when would you choose one over the other?
What they’re really testing: Whether you understand the tools conceptually or just know how to copy-paste annotations.
Strong answer covers: JUnit 5’s modular architecture (Platform / Jupiter / Vintage) vs TestNG’s built-in parallelism and dependency ordering; choosing JUnit 5 as the Spring Boot default for new NZ Java projects; reaching for TestNG only when you have a concrete parallel-execution or cross-test dependency problem.
When would you use TestNG’s @DataProvider over JUnit 5’s @ParameterizedTest with a @MethodSource?
What they’re really testing: Depth of hands-on experience with data-driven testing patterns, not just knowledge of annotation names.
Strong answer covers: @DataProvider supports lazy iteration and method-level control (good for large datasets from a database); @MethodSource is cleaner when data lives inline or in a static factory; mention a real scenario such as parameterising Revenue NZ tax-bracket boundary tests or ListRight category-filter edge cases.
You’re joining an CloudBooks NZ team where the Java integration suite takes 45 minutes to run in GitHub Actions and is blocking every pull request. How would you approach fixing it?
What they’re really testing: Problem-solving under real constraints — they want a pragmatic plan, not a textbook answer.
Strong answer covers: Profile first (JUnit 5 parallel execution with junit.jupiter.execution.parallel.enabled=true, or TestNG thread-pool in testng.xml); replace shared database fixtures with Testcontainers-isolated instances to remove contention; split the Gradle build into test (unit, fast) and integrationTest tasks so unit tests block the PR and integration runs async.
Your JUnit 5 tests all pass locally but intermittently fail on the CI server — sometimes different tests fail each run. How do you diagnose this?
What they’re really testing: Ability to reason about shared state, ordering sensitivity, and environment differences — a common pain point in real Java projects.
Strong answer covers: Shared mutable state between tests (static fields, singleton beans not reset); timezone or locale differences between your Mac and the Linux CI runner (common in NZ teams where developers use macOS but CI runs Ubuntu); enable @TestMethodOrder and --fail-fast to isolate; use @DirtiesContext in Spring tests to force context reload rather than sharing.
How would you structure a JUnit 5 test suite for a new Spring Boot microservice so it stays maintainable as the codebase grows from 10 to 100+ services at a company like TechServNZ or Harbour Bank?
What they’re really testing: Architectural thinking and whether you’ve felt the pain of test suites that become hard to maintain at scale.
Strong answer covers: Separate unit (src/test/java), integration (src/integrationTest/java), and contract tests by Gradle source set; enforce a JaCoCo 80% coverage gate in CI; use a shared Maven/Gradle BOM to pin JUnit, Mockito, and AssertJ versions across all services; define a custom @UnitTest annotation as a meta-annotation combining @Tag and @ExtendWith(MockitoExtension.class) so developers don’t repeat boilerplate.