# Testing Guidelines

> Unit test conventions, mock strategies, and coverage requirements for Cosmic Java plugins.

---

## Scope

These guidelines apply to Cosmic/BOS Java plugin unit tests (JUnit 4 + Mockito). They do NOT apply to Enterprise C# / IronPython plugin testing.

---

## Framework & Infrastructure

| Item | Standard |
|------|----------|
| Test framework | JUnit 4 (`@Test`, `@Before`, `@After`) |
| Mock framework | Mockito 2.x + MockedStatic |
| Data construction | `DynamicObjectMocker` (chained `.add(key, value)`) |
| Reflection | `ReflectHelper.invokeProtectedMethod()` for protected, `ReflectHelper.invokeStaticMethod()` for private static |
| Base (no ResManager) | Extend `AbstractJunitNoDependenciesTest` |
| Base (with ResManager) | Extend `AbstractJunitNoDependenciesResManagerTest` |

---

## Mockito Import Rules

**Prohibited**: Wildcard static imports.
```java
// BAD — prohibited
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
```

**Required**: Explicit individual imports.
```java
// GOOD — explicit
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.mock;
// ... add each as needed
```

---

## Test Quality Rules

| Rule | Description |
|------|-------------|
| Meaningful assertions | Every `@Test` must contain at least one valid `assert` or `verify` call. `assertTrue(true)` is prohibited. |
| Early return coverage | Use `verify(mock, never()).method()` to verify downstream calls were NOT triggered. |
| Switch branch coverage | Each `case` must be a separate test method. |
| If/else coverage | Cover all condition combinations (Cartesian product) for if/else branches. |
| Boundary values | Test both sides: "just meets" AND "just misses" for each comparison operator. |

| Operator | Values to cover |
|----------|-----------------|
| `> 0` | = 0 (fail), > 0 (pass) |
| `>= 0` | = 0 (pass), < 0 (fail) |
| `a.compareTo(b) > 0` | a==b (fail), a>b (pass), a<b (fail) |
| `a.compareTo(b) >= 0` | a<b (fail), a==b (pass), a>b (pass) |
| `size() > N` | size==N (fail), size==N+1 (pass) |
| `isEmpty()` | empty, exactly 1, multiple |

---

## DynamicObject Assertion Rules

| Scenario | Assertion |
|----------|-----------|
| Empty string | `assertEquals("", obj.getString(...))` |
| Empty number | `assertEquals(0L, obj.getLong(...))` |
| Empty BigDecimal | `assertEquals(BigDecimal.ZERO, obj.getBigDecimal(...))` |

---

## Test Lifecycle Rules

- All `MockedStatic` instances must be `.close()`d in `@After` — no state leakage between tests
- `@UnittestCaseInfo` and `@DisplayName` annotations are **required** on every `@Test` method
- `@UnittestCaseInfo.author`: `<name> <name@kingdee.com>` format
- `@UnittestCaseInfo.lastUpdateTime`: `yyyy-MM-dd HH:mm:ss`
- Must include `targetClass`, `targetMethod`, `methodSignature`, `testPoints`

---

## BaseTest Inheritance Rules

1. Read the `BaseTest` source to identify pre-initialized `MockedStatic` fields
2. Subtract pre-initialized mocks from required mocks — test class declares only the **difference**
3. In `@Before`: only init the difference set. Call `super.before()` first, then own mocks
4. In `@After`: only close the difference set

