Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/README.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-instructions) for guidelines on
| [Java MCP Server Development Guidelines](../instructions/java-mcp-server.instructions.md)<br />[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/instructions?url=vscode%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fjava-mcp-server.instructions.md)<br />[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/instructions?url=vscode-insiders%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fjava-mcp-server.instructions.md) | Best practices and patterns for building Model Context Protocol (MCP) servers in Java using the official MCP Java SDK with reactive streams and Spring integration. |
| [Joyride User Scripts Project Assistant](../instructions/joyride-user-project.instructions.md)<br />[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/instructions?url=vscode%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fjoyride-user-project.instructions.md)<br />[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/instructions?url=vscode-insiders%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fjoyride-user-project.instructions.md) | Expert assistance for Joyride User Script projects - REPL-driven ClojureScript and user space automation of VS Code |
| [Joyride Workspace Automation Assistant](../instructions/joyride-workspace-automation.instructions.md)<br />[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/instructions?url=vscode%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fjoyride-workspace-automation.instructions.md)<br />[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/instructions?url=vscode-insiders%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fjoyride-workspace-automation.instructions.md) | Expert assistance for Joyride Workspace automation - REPL-driven and user space ClojureScript automation within specific VS Code workspaces |
| [JUnit 5 Assertions Best Practices](../instructions/java-junit5-assertions.instructions.md)<br />[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/instructions?url=vscode%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fjava-junit5-assertions.instructions.md)<br />[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/instructions?url=vscode-insiders%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fjava-junit5-assertions.instructions.md) | Standardizes JUnit 5 (Jupiter) assertions with best practices for performance, readability, and modern features (5.8+). Covers Supplier messages, assertAll, assertThrowsExactly, and performance-critical timeouts. |
| [Kotlin MCP Server Development Guidelines](../instructions/kotlin-mcp-server.instructions.md)<br />[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/instructions?url=vscode%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fkotlin-mcp-server.instructions.md)<br />[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/instructions?url=vscode-insiders%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fkotlin-mcp-server.instructions.md) | Best practices and patterns for building Model Context Protocol (MCP) servers in Kotlin using the official io.modelcontextprotocol:kotlin-sdk library. |
| [Kubernetes Deployment Best Practices](../instructions/kubernetes-deployment-best-practices.instructions.md)<br />[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/instructions?url=vscode%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fkubernetes-deployment-best-practices.instructions.md)<br />[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/instructions?url=vscode-insiders%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fkubernetes-deployment-best-practices.instructions.md) | Comprehensive best practices for deploying and managing applications on Kubernetes. Covers Pods, Deployments, Services, Ingress, ConfigMaps, Secrets, health checks, resource limits, scaling, and security contexts. |
| [Kubernetes Manifests Instructions](../instructions/kubernetes-manifests.instructions.md)<br />[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/instructions?url=vscode%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fkubernetes-manifests.instructions.md)<br />[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/instructions?url=vscode-insiders%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fkubernetes-manifests.instructions.md) | Best practices for Kubernetes YAML manifests including labeling conventions, security contexts, pod security, resource management, probes, and validation commands |
Expand Down
165 changes: 165 additions & 0 deletions instructions/java-junit5-assertions.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
---
description: "Standardizes JUnit 5 (Jupiter) assertions with best practices for performance, readability, and modern features (5.8+). Covers Supplier messages, assertAll, assertThrowsExactly, and performance-critical timeouts."
applyTo: "**/*Test.java, **/*IT.java, **/*Steps.java, **/*StepDefs.java"
---

# JUnit 5 Assertions Best Practices

Follow these best practices when writing, reviewing, or refactoring Java test code with JUnit Jupiter (JUnit 5). These rules focus on test accuracy, performance (lazy evaluation), and leveraging modern Jupiter features.

## 1. Imports

Prefer static imports for assertions to reduce boilerplate. Unless your team conventions dictate otherwise, prefer explicit imports over wildcard (`*`) imports.

```java
// ❌ BAD β€” verbose and clutters the test method
Assertions.assertEquals(expected, actual);

// ❌ BAD β€” wildcard import (unless standard in your team)
import static org.junit.jupiter.api.Assertions.*;

// βœ… GOOD β€” explicit static import
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

assertEquals(expected, actual);
```

> **Best for**: Improving readability and keeping test methods focused on logic. Always import from `org.junit.jupiter.api.Assertions`.

## 2. assertEquals β€” Expected Value First

`expected` is always the **first** argument, `actual` is always **second**.

```java
// ❌ BAD β€” swapped; failure message is misleading
assertEquals(calculator.add(1, 1), 2);

// βœ… GOOD
assertEquals(2, calculator.add(1, 1));

// βœ… GOOD β€” floating point: always provide a delta
assertEquals(0.3, 0.1 + 0.2, 1e-9);
```

> **Best for**: Ensuring failure logs correctly report "Expected [X] but was [Y]".

## 3. Failure Messages β€” Supplier vs String

Pass failure messages as a `Supplier<String>` when the message construction is expensive (e.g., string formatting or complex object inspection).

```java
// ❌ BAD β€” expensive message constructed even when the assertion passes
assertEquals(expected, actual, "Expected %s but got %s".formatted(expected, actual));

// βœ… GOOD β€” evaluated only on failure (Lazy evaluation)
assertEquals(expected, actual,
() -> "Expected %s but got %s".formatted(expected, actual));

// βœ… GOOD β€” simple, constant string literal (zero overhead)
assertTrue(isActive, "User account must be active");
```

> **Best for**: Performance-critical test suites and complex diagnostic messages.

## 4. assertAll β€” Group Related Assertions

Use `assertAll` when checking multiple properties of the same result. All assertions run even if earlier ones fail.

```java
// ❌ BAD β€” stops at first failure; other properties go unchecked
assertEquals("Jane", person.firstName());
assertEquals("Doe", person.lastName());

// βœ… GOOD
assertAll("person",
() -> assertEquals("Jane", person.firstName()),
() -> assertEquals("Doe", person.lastName()),
() -> assertEquals(30, person.age())
);
```

> **Best for**: Comprehensive object state verification and avoiding "partial failure" ambiguity.

## 5. Exception Testing β€” assertThrows vs assertThrowsExactly

`assertThrows` returns the exception for further verification. Use `assertThrowsExactly` for strict type matching.

```java
// βœ… assertThrows β€” passes if thrown type IS-A expected type (subclasses accepted)
ArithmeticException ex = assertThrows(
ArithmeticException.class,
() -> calculator.divide(1, 0)
);
assertEquals("/ by zero", ex.getMessage());

// βœ… assertThrowsExactly β€” passes ONLY if type matches EXACTLY (JUnit 5.8+)
assertThrowsExactly(IllegalArgumentException.class, () -> {
throw new IllegalArgumentException("invalid");
});
```

> **Best for**: `assertThrows` for general hierarchy testing; `assertThrowsExactly` when the precise implementation class is part of the API contract.

## 6. assertDoesNotThrow

Use when the absence of an exception is the explicit contract being tested.

```java
// βœ… GOOD β€” captures and returns the result for further assertions
int result = assertDoesNotThrow(() -> service.calculate(data));
assertEquals(100, result);
```

> **Best for**: Explicitly documenting that a specific edge case should not trigger an error.

## 7. Performance & Deadlines β€” assertTimeout

Use `assertTimeout` to ensure execution completes within a limit. Use `assertTimeoutPreemptively` only when hard-abortion is required.

```java
// βœ… assertTimeout β€” waits for completion, then checks duration
assertTimeout(Duration.ofSeconds(1), () -> service.heavyTask());

// ⚠️ assertTimeoutPreemptively β€” hard-aborts at deadline (Separate thread)
// Warning: ThreadLocal state (@Transactional) does NOT propagate.
assertTimeoutPreemptively(Duration.ofMillis(500), () -> service.fastTask());
```

> **Best for**: SLA verification and preventing hanging tests in CI/CD pipelines.

## 8. Type Safety β€” assertInstanceOf

Prefer `assertInstanceOf` (JUnit 5.8+) over `assertTrue` + `instanceof` to get automatic casting.

```java
// ❌ BAD β€” requires manual cast after assertion
assertTrue(result instanceof SuccessResponse);

// βœ… GOOD β€” returns the casted object
SuccessResponse resp = assertInstanceOf(SuccessResponse.class, result);
assertEquals(200, resp.statusCode());
```

> **Best for**: Testing polymorphic results and reducing boilerplate casting.

## 9. Collections and Arrays

Use dedicated assertions for deep comparison and informative diffs.

```java
// βœ… assertIterableEquals β€” element-by-element deep diff on failure
assertIterableEquals(expectedList, actualList);

// βœ… assertArrayEquals β€” deep comparison for arrays
assertArrayEquals(expectedArray, actualArray);
```

> **Best for**: Verifying list order and complex data structure contents.

## 10. Anti-Patterns

- **Misusing `assertTrue` for Equality:** Do not use `assertTrue(result == 42)`. Use `assertEquals(42, result)` to see both values in logs.
- **Substituting `assertNotNull` for real checks:** Don't just check for null if you can check the value. `assertEquals(expected, result)` is always better than `assertNotNull(result)`.
- **Suppressing Failures:** Never catch `AssertionError` to hide a failure.
- **Legacy Imports:** Do not mix `org.junit.Assert` (JUnit 4) with JUnit 5 tests.
Loading