- Structure tests with `given:`, `when:`, `then:` blocks — use `expect:` for pure assertions with no state changes.
- Parameterise with `where:` data tables: `where: a | b | expected; 1 | 2 | 3; 4 | 5 | 9` — each column row is a separate test case.
- Create mocks with `Mock(ServiceClass)` — stub returns with `service.method() >> returnValue` and verify calls with `1 * service.save(_)`.
- Use `Spy(RealClass)` for partial mocking — calls through to real methods unless explicitly stubbed with `>>` or `>>>` chained closures.
- Assert exceptions with `thrown(IllegalArgumentException)` in `then:` — use `notThrown(Exception)` for the absence of exceptions.
- Add `@Unroll` to expand parameterised test names: `@Unroll "adding #a to #b gives #expected"` — uses `#variable` interpolation in the method name.
- Use `with(object) { name == "Alice"; age == 30 }` for grouped property assertions — cleaner than multiple bare assertions.
- Extend `Specification` in every test — add `@Subject SomeClass subject = new SomeClass()` to make the class under test explicit.