0
# Test Sandbox
1
2
File system utilities for creating isolated test directories and managing test data with automatic cleanup capabilities.
3
4
## Capabilities
5
6
### TestSandbox Class
7
8
Main class for creating and managing isolated test directories.
9
10
```typescript { .api }
11
/**
12
* TestSandbox class provides a convenient way to get a reference to a
13
* sandbox folder in which you can perform operations for testing purposes
14
*/
15
class TestSandbox {
16
/**
17
* Create a new TestSandbox instance
18
* @param rootPath - Root path for the sandbox (resolved against current directory if relative)
19
* @param options - Configuration options for sandbox creation
20
*/
21
constructor(rootPath: string, options?: TestSandboxOptions);
22
23
/**
24
* Get the absolute path to the sandbox directory
25
* @throws Error if the sandbox has been deleted
26
*/
27
get path(): string;
28
29
/**
30
* Reset the TestSandbox by removing all files in it
31
* Also clears require cache for any files in the sandbox path
32
*/
33
reset(): Promise<void>;
34
35
/**
36
* Delete the TestSandbox completely
37
* After deletion, the sandbox instance becomes unusable
38
*/
39
delete(): Promise<void>;
40
41
/**
42
* Create a directory in the TestSandbox
43
* @param dir - Directory path relative to sandbox root
44
*/
45
mkdir(dir: string): Promise<void>;
46
47
/**
48
* Copy a file from source to the TestSandbox
49
* Handles source maps for .js files automatically
50
* @param src - Absolute path of source file
51
* @param dest - Destination filename (relative to sandbox), defaults to original filename
52
* @param transform - Optional function to transform file content
53
*/
54
copyFile(
55
src: string,
56
dest?: string,
57
transform?: (content: string) => string
58
): Promise<void>;
59
60
/**
61
* Create a JSON file in the TestSandbox
62
* @param dest - Destination filename (relative to sandbox)
63
* @param data - Data to serialize as JSON
64
*/
65
writeJsonFile(dest: string, data: unknown): Promise<void>;
66
67
/**
68
* Create a text file in the TestSandbox
69
* @param dest - Destination filename (relative to sandbox)
70
* @param data - Text content to write
71
*/
72
writeTextFile(dest: string, data: string): Promise<void>;
73
}
74
75
/**
76
* Configuration options for TestSandbox
77
*/
78
interface TestSandboxOptions {
79
/**
80
* Controls subdirectory creation:
81
* - true: Creates unique temporary subdirectory (default)
82
* - false: Uses root path directly
83
* - string: Creates named subdirectory
84
*/
85
subdir: boolean | string;
86
}
87
```
88
89
**Usage Examples:**
90
91
```typescript
92
import { TestSandbox, expect } from "@loopback/testlab";
93
import path from "path";
94
import fs from "fs";
95
96
// Basic sandbox usage
97
const sandbox = new TestSandbox("/tmp/my-tests");
98
99
// Create test files
100
await sandbox.writeTextFile("config.txt", "debug=true\nport=3000");
101
await sandbox.writeJsonFile("package.json", {
102
name: "test-app",
103
version: "1.0.0"
104
});
105
106
// Create directories
107
await sandbox.mkdir("src");
108
await sandbox.mkdir("src/controllers");
109
110
// Work with files
111
const configPath = path.join(sandbox.path, "config.txt");
112
const content = fs.readFileSync(configPath, "utf8");
113
expect(content).to.equal("debug=true\nport=3000");
114
115
// Clean up
116
await sandbox.reset(); // Remove all files but keep sandbox
117
// or
118
await sandbox.delete(); // Remove entire sandbox
119
```
120
121
### Sandbox Creation Options
122
123
Different ways to create and configure sandboxes.
124
125
**Usage Examples:**
126
127
```typescript
128
import { TestSandbox } from "@loopback/testlab";
129
130
// Default: Creates unique temporary subdirectory
131
const sandbox1 = new TestSandbox("/tmp/tests");
132
// Creates: /tmp/tests/{process.pid}{random}
133
134
// Use root path directly
135
const sandbox2 = new TestSandbox("/tmp/tests", {subdir: false});
136
// Uses: /tmp/tests
137
138
// Create named subdirectory
139
const sandbox3 = new TestSandbox("/tmp/tests", {subdir: "test-suite-1"});
140
// Creates: /tmp/tests/test-suite-1
141
142
// Multiple sandboxes with same root (for parallel tests)
143
const sandbox4 = new TestSandbox("/tmp/tests"); // /tmp/tests/{pid}{random1}
144
const sandbox5 = new TestSandbox("/tmp/tests"); // /tmp/tests/{pid}{random2}
145
```
146
147
### File Operations
148
149
Comprehensive file and directory operations within the sandbox.
150
151
**Usage Examples:**
152
153
```typescript
154
import { TestSandbox, expect } from "@loopback/testlab";
155
import path from "path";
156
157
const sandbox = new TestSandbox("/tmp/file-ops-test");
158
159
// Write different types of files
160
await sandbox.writeTextFile("README.md", "# Test Project\n\nThis is a test.");
161
162
await sandbox.writeJsonFile("config.json", {
163
database: {
164
host: "localhost",
165
port: 5432
166
},
167
features: ["auth", "logging"]
168
});
169
170
// Create directory structure
171
await sandbox.mkdir("src");
172
await sandbox.mkdir("src/models");
173
await sandbox.mkdir("test");
174
175
// Copy external files
176
const sourceFile = "/path/to/template.js";
177
await sandbox.copyFile(sourceFile); // Uses original filename
178
await sandbox.copyFile(sourceFile, "custom-name.js"); // Custom filename
179
180
// Copy with transformation
181
await sandbox.copyFile(sourceFile, "modified.js", (content) => {
182
return content.replace(/{{PROJECT_NAME}}/g, "my-test-project");
183
});
184
185
// Verify file structure
186
const files = fs.readdirSync(sandbox.path);
187
expect(files).to.containEql("README.md");
188
expect(files).to.containEql("config.json");
189
expect(files).to.containEql("src");
190
191
// Read created files
192
const configPath = path.join(sandbox.path, "config.json");
193
const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
194
expect(config.database.host).to.equal("localhost");
195
```
196
197
### Test Lifecycle Management
198
199
Proper sandbox management in test suites.
200
201
**Usage Examples:**
202
203
```typescript
204
import { TestSandbox, expect } from "@loopback/testlab";
205
206
describe("File processing tests", () => {
207
let sandbox: TestSandbox;
208
209
beforeEach(async () => {
210
sandbox = new TestSandbox("/tmp/test-processing");
211
});
212
213
afterEach(async () => {
214
await sandbox.delete();
215
});
216
217
it("should process configuration files", async () => {
218
// Setup test data
219
await sandbox.writeJsonFile("input.json", {
220
name: "test",
221
version: "1.0.0"
222
});
223
224
// Run the function being tested
225
const result = await processConfigFile(
226
path.join(sandbox.path, "input.json")
227
);
228
229
// Verify results
230
expect(result.name).to.equal("test");
231
expect(result.version).to.equal("1.0.0");
232
});
233
234
it("should handle missing files gracefully", async () => {
235
const missingFile = path.join(sandbox.path, "nonexistent.json");
236
237
await expect(processConfigFile(missingFile))
238
.to.be.rejectedWith(/File not found/);
239
});
240
});
241
242
// Alternative: Reset instead of delete for better performance
243
describe("Multiple test cases", () => {
244
let sandbox: TestSandbox;
245
246
before(async () => {
247
sandbox = new TestSandbox("/tmp/multi-tests");
248
});
249
250
beforeEach(async () => {
251
await sandbox.reset(); // Clear files but keep directory
252
});
253
254
after(async () => {
255
await sandbox.delete(); // Final cleanup
256
});
257
258
// ... tests
259
});
260
```
261
262
### Advanced Patterns
263
264
Complex usage patterns and integration scenarios.
265
266
**Usage Examples:**
267
268
```typescript
269
import { TestSandbox, expect } from "@loopback/testlab";
270
import { spawn } from "child_process";
271
import util from "util";
272
273
const execFile = util.promisify(require("child_process").execFile);
274
275
// Testing CLI tools
276
async function testCliTool() {
277
const sandbox = new TestSandbox("/tmp/cli-test");
278
279
// Create input files
280
await sandbox.writeJsonFile("package.json", {
281
name: "test-package",
282
scripts: {
283
build: "echo 'Building...'"
284
}
285
});
286
287
// Run CLI tool in sandbox
288
const result = await execFile("npm", ["run", "build"], {
289
cwd: sandbox.path
290
});
291
292
expect(result.stdout).to.match(/Building/);
293
294
await sandbox.delete();
295
}
296
297
// Testing file transformations
298
async function testFileTransformation() {
299
const sandbox = new TestSandbox("/tmp/transform-test");
300
301
// Create source files
302
await sandbox.writeTextFile("template.html", `
303
<html>
304
<title>{{TITLE}}</title>
305
<body>{{CONTENT}}</body>
306
</html>
307
`);
308
309
// Transform file
310
const templatePath = path.join(sandbox.path, "template.html");
311
const template = fs.readFileSync(templatePath, "utf8");
312
const rendered = template
313
.replace("{{TITLE}}", "Test Page")
314
.replace("{{CONTENT}}", "<h1>Hello World</h1>");
315
316
await sandbox.writeTextFile("output.html", rendered);
317
318
// Verify transformation
319
const outputPath = path.join(sandbox.path, "output.html");
320
const output = fs.readFileSync(outputPath, "utf8");
321
expect(output).to.match(/<title>Test Page<\/title>/);
322
expect(output).to.match(/<h1>Hello World<\/h1>/);
323
324
await sandbox.delete();
325
}
326
327
// Testing module loading and require cache
328
async function testModuleLoading() {
329
const sandbox = new TestSandbox("/tmp/module-test");
330
331
// Create a module
332
await sandbox.writeTextFile("calculator.js", `
333
exports.add = (a, b) => a + b;
334
exports.multiply = (a, b) => a * b;
335
`);
336
337
// Load and test module
338
const calculatorPath = path.join(sandbox.path, "calculator.js");
339
const calculator = require(calculatorPath);
340
341
expect(calculator.add(2, 3)).to.equal(5);
342
expect(calculator.multiply(4, 5)).to.equal(20);
343
344
// Modify module
345
await sandbox.writeTextFile("calculator.js", `
346
exports.add = (a, b) => a + b + 1; // Modified
347
exports.multiply = (a, b) => a * b;
348
`);
349
350
// Reset clears require cache automatically
351
await sandbox.reset();
352
353
// Recreate and reload
354
await sandbox.writeTextFile("calculator.js", `
355
exports.add = (a, b) => a + b + 1;
356
exports.multiply = (a, b) => a * b;
357
`);
358
359
const newCalculator = require(calculatorPath);
360
expect(newCalculator.add(2, 3)).to.equal(6); // Uses new implementation
361
362
await sandbox.delete();
363
}
364
```
365
366
### Error Handling
367
368
Proper error handling with sandbox operations.
369
370
**Usage Examples:**
371
372
```typescript
373
import { TestSandbox, expect } from "@loopback/testlab";
374
375
// Handle deleted sandbox
376
const sandbox = new TestSandbox("/tmp/error-test");
377
await sandbox.delete();
378
379
try {
380
const path = sandbox.path; // Throws error
381
} catch (error) {
382
expect(error.message).to.match(/TestSandbox instance was deleted/);
383
}
384
385
// Handle file operation errors
386
const validSandbox = new TestSandbox("/tmp/valid-test");
387
388
try {
389
await validSandbox.copyFile("/nonexistent/file.txt");
390
} catch (error) {
391
expect(error.code).to.equal("ENOENT");
392
}
393
394
// Handle permission errors
395
try {
396
const restrictedSandbox = new TestSandbox("/root/restricted");
397
} catch (error) {
398
expect(error.code).to.equal("EACCES");
399
}
400
401
await validSandbox.delete();
402
```