Skip to content

Commit 628f247

Browse files
committed
docs: add page describing the module mocking API
1 parent ae74668 commit 628f247

3 files changed

Lines changed: 172 additions & 1 deletion

File tree

website/src/docs/api/_meta.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@
1414
"name": "mocking-and-spying",
1515
"label": "Mocking & Spying"
1616
},
17+
{
18+
"type": "file",
19+
"name": "module-mocking",
20+
"label": "Module Mocking"
21+
},
1722
{
1823
"type": "file",
1924
"name": "rendering-components",

website/src/docs/api/mocking-and-spying.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,4 +197,6 @@ Harness provides the complete Vitest spy and mock API including:
197197
- **Mock Functions**: `fn()`, mock implementations, return values
198198
- **Spying**: `spyOn()`, method restoration
199199
- **Mock Management**: `clearAllMocks()`, `resetAllMocks()`, `restoreAllMocks()`
200-
- **Spy Assertions**: All `toHaveBeenCalled*` and `toHaveReturned*` matchers
200+
- **Spy Assertions**: All `toHaveBeenCalled*` and `toHaveReturned*` matchers
201+
202+
For module mocking capabilities, see the [Module Mocking](/docs/api/module-mocking) documentation.
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
# Module Mocking
2+
3+
Harness provides powerful module mocking capabilities that allow you to replace entire modules or parts of modules with mock implementations. This is particularly useful for testing React Native code that depends on native modules or third-party libraries.
4+
5+
## mock()
6+
7+
Mock a module by providing a factory function that returns the mock implementation.
8+
9+
```typescript
10+
import { describe, test, expect, mock, fn } from 'react-native-harness'
11+
12+
describe('module mocking', () => {
13+
test('complete module mock', () => {
14+
const mockFactory = () => ({
15+
formatString: fn().mockReturnValue('mocked string'),
16+
calculateSum: fn().mockImplementation(
17+
(a: number, b: number) => a + b + 1000
18+
),
19+
constants: {
20+
VERSION: '999.0.0',
21+
DEBUG: true,
22+
},
23+
})
24+
25+
mock('react-native', mockFactory)
26+
27+
const mockedModule = require('react-native')
28+
29+
expect(mockedModule.formatString()).toBe('mocked string')
30+
expect(mockedModule.calculateSum(5, 10)).toBe(1015)
31+
expect(mockedModule.constants.VERSION).toBe('999.0.0')
32+
expect(mockedModule.formatString).toHaveBeenCalledTimes(1)
33+
})
34+
})
35+
```
36+
37+
## requireActual()
38+
39+
Get the actual (unmocked) implementation of a module. This is useful for partial mocking where you want to preserve some exports while replacing others.
40+
41+
```typescript
42+
import { describe, test, expect, mock, requireActual, fn } from 'react-native-harness'
43+
44+
describe('partial module mocking', () => {
45+
test('mock only Platform while keeping other exports', () => {
46+
const mockFactory = () => {
47+
// Get the actual react-native module
48+
const actualRN = requireActual('react-native')
49+
50+
// Copy without invoking getters to avoid triggering lazy initialization
51+
const proto = Object.getPrototypeOf(actualRN)
52+
const descriptors = Object.getOwnPropertyDescriptors(actualRN)
53+
54+
const mockedRN = Object.create(proto, descriptors)
55+
const mockedPlatform = {
56+
OS: 'mockOS',
57+
Version: 999,
58+
select: fn().mockImplementation((options: Record<string, any>) => {
59+
return options.mockOS || options.default
60+
}),
61+
isPad: false,
62+
isTesting: true,
63+
}
64+
65+
Object.defineProperty(mockedRN, 'Platform', {
66+
get() {
67+
return mockedPlatform
68+
},
69+
})
70+
71+
return mockedRN
72+
}
73+
74+
mock('react-native', mockFactory)
75+
76+
const mockedRN = require('react-native')
77+
78+
// Verify Platform is mocked
79+
expect(mockedRN.Platform.OS).toBe('mockOS')
80+
expect(mockedRN.Platform.Version).toBe(999)
81+
82+
// Verify other React Native exports are preserved
83+
expect(mockedRN).toHaveProperty('View')
84+
expect(mockedRN).toHaveProperty('Text')
85+
expect(mockedRN).toHaveProperty('StyleSheet')
86+
})
87+
})
88+
```
89+
90+
## unmock()
91+
92+
Remove a mock for a specific module, restoring it to its original implementation.
93+
94+
```typescript
95+
import { describe, test, expect, mock, unmock } from 'react-native-harness'
96+
97+
describe('unmocking modules', () => {
98+
test('unmock a previously mocked module', () => {
99+
// Mock a module
100+
const mockFactory = () => ({ mockProperty: 'mocked' })
101+
mock('react-native', mockFactory)
102+
103+
// Verify it's mocked
104+
let module = require('react-native')
105+
expect(module.mockProperty).toBe('mocked')
106+
107+
// Unmock it
108+
unmock('react-native')
109+
110+
// Verify it's back to actual
111+
module = require('react-native')
112+
expect(module).not.toHaveProperty('mockProperty')
113+
expect(module).toHaveProperty('Platform') // Should have actual RN properties
114+
})
115+
})
116+
```
117+
118+
## resetModules()
119+
120+
Clear all module mocks and the module cache. This is useful in `afterEach` hooks to ensure tests don't interfere with each other.
121+
122+
```typescript
123+
import { describe, test, expect, mock, resetModules, afterEach } from 'react-native-harness'
124+
125+
describe('module reset', () => {
126+
afterEach(() => {
127+
resetModules()
128+
})
129+
130+
test('reinitialize module after reset', () => {
131+
const mockFactory = () => ({ now: Math.random() })
132+
133+
mock('react-native', mockFactory)
134+
135+
// Verify mock is active
136+
const oldNow = require('react-native').now
137+
138+
// Reset all modules
139+
resetModules()
140+
141+
// Require again, should reinitialize the module
142+
const newNow = require('react-native').now
143+
expect(newNow).not.toBe(oldNow)
144+
})
145+
})
146+
```
147+
148+
## Best Practices
149+
150+
1. **Always reset modules in `afterEach`**: Use `resetModules()` in your test cleanup to prevent mocks from leaking between tests.
151+
152+
2. **Use `requireActual` for partial mocks**: When you only need to mock specific exports, use `requireActual()` to preserve the rest of the module.
153+
154+
3. **Factory functions are called lazily**: The factory function is only called when the module is first required, not when `mock()` is called.
155+
156+
4. **Module caching**: Modules are cached after first require. Use `resetModules()` if you need to reinitialize a mocked module.
157+
158+
## API Reference
159+
160+
- **`mock(moduleId: string, factory: () => unknown): void`** - Mock a module with a factory function
161+
- **`unmock(moduleId: string): void`** - Remove a mock for a specific module
162+
- **`requireActual<T = any>(moduleId: string): T`** - Get the actual (unmocked) implementation of a module
163+
- **`resetModules(): void`** - Clear all module mocks and the module cache
164+

0 commit comments

Comments
 (0)