| name | zunit |
| description | Generate and run zunit tests for java-cli-app projects. Use when asked to create tests, write tests, add tests, or generate test files for a java-cli-app project. Triggers on "zunit", "write tests", "create tests", "add tests", "test this", "generate tests", or requests to test a java-cli-app application. Also trigger when the user asks to verify or validate behavior of a java-cli-app project. Not for JUnit tests or microprofile-server projects โ use continuous-testing for those. |
Generate and run zunit tests for a java-cli-app project using $ARGUMENTS. Apply all rules below strictly.
What is zunit
zunit is a zero-dependency test runner. It discovers *Test.java source files and runs each directly via java --source 25. No compilation step, no JUnit, no framework.
Test Convention
- Test files: name ends with
Test.java, placed in test/ directory
- Each test file is a self-contained Java source script with
void main()
- Tests run via
java --source 25 --class-path <classpath>
- Failure: any thrown exception or non-zero exit = failed
- Success: clean exit with code 0
- All test files run concurrently in separate JVMs
Test File Structure
Every test file follows this pattern:
void main() {
var input = ...;
var result = SomeClass.someMethod(input);
assert expected.equals(result) : "expected %s but got %s".formatted(expected, result);
}
Assertion Style
- No assertion libraries โ use the built-in
assert statement; zunit runs every test with -ea
- Always attach a descriptive message including expected and actual values:
assert condition : message
- One test file can contain multiple assertions โ the first failure stops the file
- For testing exceptions, use try/catch and throw
AssertionError explicitly:
void main() {
try {
SomeClass.methodThatShouldFail(badInput);
throw new AssertionError("expected exception was not thrown");
} catch (IllegalArgumentException expected) {
}
}
Caveat: assert only executes with assertions enabled. Running a test file directly requires the flag: java -ea FooTest.java.
Test Discovery for java-cli-app Projects
Directory Layout
project-root/
โโโ src/main/java/ # main source (compiled by zb)
โโโ test/ # zunit test sources
โ โโโ SomethingTest.java
โ โโโ AnotherTest.java
โโโ zbo/app.jar # zb output (auto-detected as classpath)
โโโ .zb # zb config
Classpath
zunit auto-detects zbo/app.jar as the classpath. Tests import main classes directly โ zb packages everything into app.jar.
How to Generate Tests
- Read the main source code in
src/main/java/ to understand what to test
- Create test files in
test/ directory โ one test file per logical concern
- Name test files descriptively:
ParserTest.java, ValidationTest.java, OutputTest.java
- Test public behavior, not internal implementation
- Include both happy path and error/edge cases
- Keep each test file focused โ prefer multiple small test files over one large file (they run concurrently)
Test File Rules
- No package declaration
- No imports from
java.base โ it is automatically available
- Use module imports for non-base modules (e.g.,
import module java.net.http;)
- Use
var for local variables
- Use unnamed class style โ
void main() at top level, helper methods as needed
- Do not use
IO.println() for assertions โ use assert
- Printing to stdout is fine for debugging but not required
HTTP Client Timeouts
When tests make network calls using java.net.http.HttpClient, always set explicit timeouts to prevent tests from hanging:
- Set
.connectTimeout(Duration.ofSeconds(2)) on the HttpClient
- Set
.timeout(Duration.ofSeconds(2)) on each HttpRequest
Running Tests
After generating test files:
- Build first:
zb (compiles main sources into zbo/app.jar)
- Run tests:
zunit (discovers test/*Test.java, runs against zbo/app.jar)
- Alternatively:
zb && zunit
Use zunit -verbose to debug classpath or discovery issues.
Example
Given a main class in src/main/java/Converter.java:
class Converter {
static int toFahrenheit(int celsius) {
return celsius * 9 / 5 + 32;
}
}
Generate test/ConverterTest.java:
void main() {
var freezing = Converter.toFahrenheit(0);
assert freezing == 32 : "expected 32 but got " + freezing;
var boiling = Converter.toFahrenheit(100);
assert boiling == 212 : "expected 212 but got " + boiling;
var negative = Converter.toFahrenheit(-40);
assert negative == -40 : "expected -40 but got " + negative;
}