A collection of test utilities specifically designed for LoopBack 4 applications and TypeScript testing
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
File system utilities for creating isolated test directories and managing test data with automatic cleanup capabilities.
Main class for creating and managing isolated test directories.
/**
* TestSandbox class provides a convenient way to get a reference to a
* sandbox folder in which you can perform operations for testing purposes
*/
class TestSandbox {
/**
* Create a new TestSandbox instance
* @param rootPath - Root path for the sandbox (resolved against current directory if relative)
* @param options - Configuration options for sandbox creation
*/
constructor(rootPath: string, options?: TestSandboxOptions);
/**
* Get the absolute path to the sandbox directory
* @throws Error if the sandbox has been deleted
*/
get path(): string;
/**
* Reset the TestSandbox by removing all files in it
* Also clears require cache for any files in the sandbox path
*/
reset(): Promise<void>;
/**
* Delete the TestSandbox completely
* After deletion, the sandbox instance becomes unusable
*/
delete(): Promise<void>;
/**
* Create a directory in the TestSandbox
* @param dir - Directory path relative to sandbox root
*/
mkdir(dir: string): Promise<void>;
/**
* Copy a file from source to the TestSandbox
* Handles source maps for .js files automatically
* @param src - Absolute path of source file
* @param dest - Destination filename (relative to sandbox), defaults to original filename
* @param transform - Optional function to transform file content
*/
copyFile(
src: string,
dest?: string,
transform?: (content: string) => string
): Promise<void>;
/**
* Create a JSON file in the TestSandbox
* @param dest - Destination filename (relative to sandbox)
* @param data - Data to serialize as JSON
*/
writeJsonFile(dest: string, data: unknown): Promise<void>;
/**
* Create a text file in the TestSandbox
* @param dest - Destination filename (relative to sandbox)
* @param data - Text content to write
*/
writeTextFile(dest: string, data: string): Promise<void>;
}
/**
* Configuration options for TestSandbox
*/
interface TestSandboxOptions {
/**
* Controls subdirectory creation:
* - true: Creates unique temporary subdirectory (default)
* - false: Uses root path directly
* - string: Creates named subdirectory
*/
subdir: boolean | string;
}Usage Examples:
import { TestSandbox, expect } from "@loopback/testlab";
import path from "path";
import fs from "fs";
// Basic sandbox usage
const sandbox = new TestSandbox("/tmp/my-tests");
// Create test files
await sandbox.writeTextFile("config.txt", "debug=true\nport=3000");
await sandbox.writeJsonFile("package.json", {
name: "test-app",
version: "1.0.0"
});
// Create directories
await sandbox.mkdir("src");
await sandbox.mkdir("src/controllers");
// Work with files
const configPath = path.join(sandbox.path, "config.txt");
const content = fs.readFileSync(configPath, "utf8");
expect(content).to.equal("debug=true\nport=3000");
// Clean up
await sandbox.reset(); // Remove all files but keep sandbox
// or
await sandbox.delete(); // Remove entire sandboxDifferent ways to create and configure sandboxes.
Usage Examples:
import { TestSandbox } from "@loopback/testlab";
// Default: Creates unique temporary subdirectory
const sandbox1 = new TestSandbox("/tmp/tests");
// Creates: /tmp/tests/{process.pid}{random}
// Use root path directly
const sandbox2 = new TestSandbox("/tmp/tests", {subdir: false});
// Uses: /tmp/tests
// Create named subdirectory
const sandbox3 = new TestSandbox("/tmp/tests", {subdir: "test-suite-1"});
// Creates: /tmp/tests/test-suite-1
// Multiple sandboxes with same root (for parallel tests)
const sandbox4 = new TestSandbox("/tmp/tests"); // /tmp/tests/{pid}{random1}
const sandbox5 = new TestSandbox("/tmp/tests"); // /tmp/tests/{pid}{random2}Comprehensive file and directory operations within the sandbox.
Usage Examples:
import { TestSandbox, expect } from "@loopback/testlab";
import path from "path";
const sandbox = new TestSandbox("/tmp/file-ops-test");
// Write different types of files
await sandbox.writeTextFile("README.md", "# Test Project\n\nThis is a test.");
await sandbox.writeJsonFile("config.json", {
database: {
host: "localhost",
port: 5432
},
features: ["auth", "logging"]
});
// Create directory structure
await sandbox.mkdir("src");
await sandbox.mkdir("src/models");
await sandbox.mkdir("test");
// Copy external files
const sourceFile = "/path/to/template.js";
await sandbox.copyFile(sourceFile); // Uses original filename
await sandbox.copyFile(sourceFile, "custom-name.js"); // Custom filename
// Copy with transformation
await sandbox.copyFile(sourceFile, "modified.js", (content) => {
return content.replace(/{{PROJECT_NAME}}/g, "my-test-project");
});
// Verify file structure
const files = fs.readdirSync(sandbox.path);
expect(files).to.containEql("README.md");
expect(files).to.containEql("config.json");
expect(files).to.containEql("src");
// Read created files
const configPath = path.join(sandbox.path, "config.json");
const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
expect(config.database.host).to.equal("localhost");Proper sandbox management in test suites.
Usage Examples:
import { TestSandbox, expect } from "@loopback/testlab";
describe("File processing tests", () => {
let sandbox: TestSandbox;
beforeEach(async () => {
sandbox = new TestSandbox("/tmp/test-processing");
});
afterEach(async () => {
await sandbox.delete();
});
it("should process configuration files", async () => {
// Setup test data
await sandbox.writeJsonFile("input.json", {
name: "test",
version: "1.0.0"
});
// Run the function being tested
const result = await processConfigFile(
path.join(sandbox.path, "input.json")
);
// Verify results
expect(result.name).to.equal("test");
expect(result.version).to.equal("1.0.0");
});
it("should handle missing files gracefully", async () => {
const missingFile = path.join(sandbox.path, "nonexistent.json");
await expect(processConfigFile(missingFile))
.to.be.rejectedWith(/File not found/);
});
});
// Alternative: Reset instead of delete for better performance
describe("Multiple test cases", () => {
let sandbox: TestSandbox;
before(async () => {
sandbox = new TestSandbox("/tmp/multi-tests");
});
beforeEach(async () => {
await sandbox.reset(); // Clear files but keep directory
});
after(async () => {
await sandbox.delete(); // Final cleanup
});
// ... tests
});Complex usage patterns and integration scenarios.
Usage Examples:
import { TestSandbox, expect } from "@loopback/testlab";
import { spawn } from "child_process";
import util from "util";
const execFile = util.promisify(require("child_process").execFile);
// Testing CLI tools
async function testCliTool() {
const sandbox = new TestSandbox("/tmp/cli-test");
// Create input files
await sandbox.writeJsonFile("package.json", {
name: "test-package",
scripts: {
build: "echo 'Building...'"
}
});
// Run CLI tool in sandbox
const result = await execFile("npm", ["run", "build"], {
cwd: sandbox.path
});
expect(result.stdout).to.match(/Building/);
await sandbox.delete();
}
// Testing file transformations
async function testFileTransformation() {
const sandbox = new TestSandbox("/tmp/transform-test");
// Create source files
await sandbox.writeTextFile("template.html", `
<html>
<title>{{TITLE}}</title>
<body>{{CONTENT}}</body>
</html>
`);
// Transform file
const templatePath = path.join(sandbox.path, "template.html");
const template = fs.readFileSync(templatePath, "utf8");
const rendered = template
.replace("{{TITLE}}", "Test Page")
.replace("{{CONTENT}}", "<h1>Hello World</h1>");
await sandbox.writeTextFile("output.html", rendered);
// Verify transformation
const outputPath = path.join(sandbox.path, "output.html");
const output = fs.readFileSync(outputPath, "utf8");
expect(output).to.match(/<title>Test Page<\/title>/);
expect(output).to.match(/<h1>Hello World<\/h1>/);
await sandbox.delete();
}
// Testing module loading and require cache
async function testModuleLoading() {
const sandbox = new TestSandbox("/tmp/module-test");
// Create a module
await sandbox.writeTextFile("calculator.js", `
exports.add = (a, b) => a + b;
exports.multiply = (a, b) => a * b;
`);
// Load and test module
const calculatorPath = path.join(sandbox.path, "calculator.js");
const calculator = require(calculatorPath);
expect(calculator.add(2, 3)).to.equal(5);
expect(calculator.multiply(4, 5)).to.equal(20);
// Modify module
await sandbox.writeTextFile("calculator.js", `
exports.add = (a, b) => a + b + 1; // Modified
exports.multiply = (a, b) => a * b;
`);
// Reset clears require cache automatically
await sandbox.reset();
// Recreate and reload
await sandbox.writeTextFile("calculator.js", `
exports.add = (a, b) => a + b + 1;
exports.multiply = (a, b) => a * b;
`);
const newCalculator = require(calculatorPath);
expect(newCalculator.add(2, 3)).to.equal(6); // Uses new implementation
await sandbox.delete();
}Proper error handling with sandbox operations.
Usage Examples:
import { TestSandbox, expect } from "@loopback/testlab";
// Handle deleted sandbox
const sandbox = new TestSandbox("/tmp/error-test");
await sandbox.delete();
try {
const path = sandbox.path; // Throws error
} catch (error) {
expect(error.message).to.match(/TestSandbox instance was deleted/);
}
// Handle file operation errors
const validSandbox = new TestSandbox("/tmp/valid-test");
try {
await validSandbox.copyFile("/nonexistent/file.txt");
} catch (error) {
expect(error.code).to.equal("ENOENT");
}
// Handle permission errors
try {
const restrictedSandbox = new TestSandbox("/root/restricted");
} catch (error) {
expect(error.code).to.equal("EACCES");
}
await validSandbox.delete();