Skip to content

Commit f0eb073

Browse files
committed
chore(testing-docs): incremental update to the testing docs
1 parent cf5041d commit f0eb073

1 file changed

Lines changed: 122 additions & 14 deletions

File tree

docs/testing.md

Lines changed: 122 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,29 @@ The OSF Angular project uses a modular and mock-driven testing strategy. A share
66

77
---
88

9+
### Pro-tips
10+
11+
**What to test**
12+
13+
The OSF Angular testing strategy enforces 100% coverage while also serving as a guardrail for future engineers. Each test should highlight the most critical aspect of your code — what you’d want the next developer to understand before making changes. If a test fails during a refactor, it should clearly signal that a core feature was impacted, prompting them to investigate why and preserve the intended behavior.
14+
15+
---
16+
17+
**Test Data**
18+
19+
The OSF Angular Test Data module provides a centralized and consistent source of data across all unit tests. It is intended solely for use within unit tests. By standardizing test data, any changes to underlying data models will produce cascading failures, which help expose the full scope of a refactor. This is preferable to isolated or hardcoded test values, which can lead to false positives and missed regressions.
20+
21+
The strategy for structuring test data follows two principles:
22+
23+
1. Include enough data to cover all relevant permutations required by the test suite.
24+
2. Ensure the data reflects all possible states (stati) of the model.
25+
26+
**Test Scope**
27+
28+
The OSF Angular project defines a `@testing` scope that can be used for importing all testing-related modules.
29+
30+
---
31+
932
## Index
1033

1134
- [Best Practices](#best-practices)
@@ -34,8 +57,8 @@ The OSF Angular project uses a modular and mock-driven testing strategy. A share
3457
| Location | Purpose |
3558
| ----------------------- | -------------------------------------- |
3659
| `osf.testing.module.ts` | Unified test module for shared imports |
37-
| `mocks/*.mock.ts` | Mock services and tokens |
38-
| `data/*.data.ts` | Static mock data for test cases |
60+
| `src/mocks/*.mock.ts` | Mock services and tokens |
61+
| `src/data/*.data.ts` | Static mock data for test cases |
3962

4063
---
4164

@@ -93,15 +116,15 @@ This guarantees **test integrity in CI** and **prevents regressions**.
93116

94117
## Key Structure
95118

96-
### `testing/osf.testing.module.ts`
119+
### `src/testing/osf.testing.module.ts`
97120

98121
This module centralizes commonly used providers, declarations, and test utilities. It's intended to be imported into any `*.spec.ts` test file to avoid repetitive boilerplate.
99122

100123
Example usage:
101124

102125
```ts
103126
import { TestBed } from '@angular/core/testing';
104-
import { OsfTestingModule } from 'testing/osf.testing.module';
127+
import { OsfTestingModule } from '@testing/osf.testing.module';
105128

106129
beforeEach(async () => {
107130
await TestBed.configureTestingModule({
@@ -141,9 +164,9 @@ beforeEach(async () => {
141164
- `StoreMock` – mocks NgRx Store for selector and dispatch testing.
142165
- `ToastServiceMock` – injects a mock version of the UI toast service.
143166

144-
### `testing/mocks/`
167+
### Testing Mocks
145168

146-
Provides common service and token mocks to isolate unit tests from real implementations.
169+
The `src/testing/mocks/` directory provides common service and token mocks to isolate unit tests from real implementations.
147170

148171
**examples**
149172

@@ -154,11 +177,16 @@ Provides common service and token mocks to isolate unit tests from real implemen
154177

155178
---
156179

157-
### `testing/data/`
180+
### Test Data
158181

159-
Includes fake/mock data used by tests to simulate external API responses or internal state.
182+
The `src/testing/data/` directory includes fake/mock data used by tests to simulate external API responses or internal state.
160183

161-
Only use data from the `testing/data` data mocks to ensure that all data is the centralized.
184+
The OSF Angular Test Data module provides a centralized and consistent source of data across all unit tests. It is intended solely for use within unit tests. By standardizing test data, any changes to underlying data models will produce cascading failures, which help expose the full scope of a refactor. This is preferable to isolated or hardcoded test values, which can lead to false positives and missed regressions.
185+
186+
The strategy for structuring test data follows two principles:
187+
188+
1. Include enough data to cover all relevant permutations required by the test suite.
189+
2. Ensure the data reflects all possible states (stati) of the model.
162190

163191
**examples**
164192

@@ -169,11 +197,13 @@ Only use data from the `testing/data` data mocks to ensure that all data is the
169197

170198
---
171199

172-
---
173-
174200
## Testing Angular Services (with HTTP)
175201

176-
All OSF Angular services that make HTTP requests must be tested using `HttpClientTestingModule` and `HttpTestingController`.
202+
All OSF Angular services that make HTTP requests must be tested using `HttpClientTestingModule` and `HttpTestingController`. This testing style verifies both the API call itself and the logic that maps the response into application data.
203+
204+
When using HttpTestingController to flush HTTP requests in tests, only use data from the @testing/data mocks to ensure consistency and full test coverage.
205+
206+
Any error handling will also need to be tested.
177207

178208
### Setup
179209

@@ -240,8 +270,86 @@ it('should call correct endpoint and return expected data', inject(
240270

241271
---
242272

243-
## Testing NGXS
273+
## NGXS State Testing Strategy
244274

245-
- coming soon
275+
The OSF Angular strategy for NGXS state testing is to create **small integration test scenarios**. This is a deliberate departure from traditional **black box isolated** testing. The rationale is:
276+
277+
1. **NGXS actions** tested in isolation are difficult to mock and result in garbage-in/garbage-out tests.
278+
2. **NGXS selectors** tested in isolation are easy to mock but also lead to garbage-in/garbage-out outcomes.
279+
3. **NGXS states** tested in isolation are easy to invoke but provide no meaningful validation.
280+
4. **Mocking service calls** during state testing introduces false positives, since the mocked service responses may not reflect actual backend behavior.
281+
282+
This approach favors realism and accuracy over artificial test isolation.
283+
284+
### Test Outline Strategy
285+
286+
1. **Dispatch the primary action** – Kick off the state logic under test.
287+
2. **Dispatch any dependent actions** – Include any secondary actions that rely on the primary action's outcome.
288+
3. **Verify the loading selector is `true`** – Ensure the loading state is activated during the async flow.
289+
4. **Verify the service call using `HttpTestingController` and `@testing/data` mocks** – Confirm that the correct HTTP request is made and flushed with known mock data.
290+
5. **Verify the loading selector is `false`** – Ensure the loading state deactivates after the response is handled.
291+
6. **Verify the primary data selector** – Check that the core selector related to the dispatched action returns the expected state.
292+
7. **Verify any additional selectors** – Assert the output of other derived selectors relevant to the action.
293+
8. **Validate the test with `httpMock.verify()`** – Confirm that all HTTP requests were flushed and none remain unhandled:
294+
295+
```ts
296+
expect(httpMock.verify).toBeTruthy();
297+
```
298+
299+
### Example
300+
301+
This is an example of an NGXS action test that involves both a **primary action** and a **dependent action**. The dependency must be dispatched first to ensure the test environment mimics the actual runtime behavior. This pattern helps validate not only the action effects but also the full selector state after updates. All HTTP requests are flushed using the centralized `@testing/data` mocks.
302+
303+
```ts
304+
it('should test action, state and selectors', inject([HttpTestingController], (httpMock: HttpTestingController) => {
305+
let result: any[] = [];
306+
// Dependency Action
307+
store.dispatch(new GetAuthorizedStorageAddons('reference-id')).subscribe();
308+
309+
// Primary Action
310+
store.dispatch(new GetAuthorizedStorageOauthToken('account-id')).subscribe(() => {
311+
result = store.selectSnapshot(AddonsSelectors.getAuthorizedStorageAddons);
312+
});
313+
314+
// Loading selector is true
315+
const loading = store.selectSignal(AddonsSelectors.getAuthorizedStorageAddonsLoading);
316+
expect(loading()).toBeTruthy();
317+
318+
// Http request for service for dependency action
319+
let request = httpMock.expectOne('api/path/dependency/action');
320+
expect(request.request.method).toBe('GET');
321+
// @testing/data response mock
322+
request.flush(getAddonsAuthorizedStorageData());
323+
324+
// Http request for service for primary action
325+
let request = httpMock.expectOne('api/path/primary/action');
326+
expect(request.request.method).toBe('PATCH');
327+
// @testing/data response mock with updates
328+
const addonWithToken = getAddonsAuthorizedStorageData(1);
329+
addonWithToken.data.attributes.oauth_token = 'ya2.34234324534';
330+
request.flush(addonWithToken);
331+
332+
// Full testing of the dependency selector
333+
expect(result[1]).toEqual(
334+
Object({
335+
accountOwnerId: '0b441148-83e5-4f7f-b302-b07b528b160b',
336+
})
337+
);
338+
339+
// Full testing of the primary selector
340+
let oauthToken = store.selectSnapshot(AddonsSelectors.getAuthorizedStorageAddonOauthToken(result[0].id));
341+
expect(oauthToken).toBe('ya29.A0AS3H6NzDCKgrUx');
342+
343+
// Verify only the requested `account-id` was updated
344+
oauthToken = store.selectSnapshot(AddonsSelectors.getAuthorizedStorageAddonOauthToken(result[1].id));
345+
expect(oauthToken).toBe(result[1].oauthToken);
346+
347+
// Loading selector is false
348+
expect(loading()).toBeFalsy();
349+
350+
// httpMock.verify to ensure no other api calls are called.
351+
expect(httpMock.verify).toBeTruthy();
352+
}));
353+
```
246354

247355
---

0 commit comments

Comments
 (0)