Jest Hook Cleanup: Ensuring Resources Are Released
Jest provides powerful lifecycle hooks like beforeAll, beforeEach, afterAll, and afterEach to manage test setup and teardown. This challenge focuses on implementing the cleanup aspect, specifically ensuring that resources initialized in setup hooks are properly released in teardown hooks. This is crucial for preventing resource leaks and ensuring test isolation.
Problem Description
You need to implement a Jest test suite for a hypothetical Resource class. This Resource class has initialize and release methods. The initialize method should be called before each test (beforeEach) to set up a new instance of the resource. The release method should be called after each test (afterEach) to clean up the initialized resource.
Your task is to write the Jest tests that verify the correct execution of initialize and release methods. You should ensure that initialize is called exactly once before each test and release is called exactly once after each test, and that they operate on the same resource instance.
Key Requirements:
ResourceClass Simulation: Create a mock or simulatedResourceclass withinitializeandreleasemethods. These methods should track whether they have been called and which resource instance they are operating on.- Jest Hooks Implementation: Utilize
beforeEachandafterEachhooks in your Jest test suite. - Verification: Write assertions to verify:
- That
initializeis called before each test begins. - That
releaseis called after each test completes. - That the same
Resourceinstance is passed to bothinitializeandreleasewithin a single test's lifecycle. - That no resource is left unreleased after all tests have completed (if applicable, consider a scenario where a global resource might be initialized in
beforeAlland released inafterAll).
- That
Expected Behavior:
For a test suite with N tests, initialize should be called N times, and release should be called N times. Each call to initialize should be followed by its corresponding test execution, and then by a call to release with the same resource instance.
Examples
Example 1:
Consider a simple test suite with one test:
// Hypothetical Resource class (you'll simulate this or use a mock)
class Resource {
public id: string;
private initialized: boolean = false;
private released: boolean = false;
constructor(id: string) {
this.id = id;
}
initialize(): void {
console.log(`Initializing resource ${this.id}`);
this.initialized = true;
}
release(): void {
console.log(`Releasing resource ${this.id}`);
this.released = true;
}
isInitialized(): boolean { return this.initialized; }
isReleased(): boolean { return this.released; }
}
// --- Jest Test Suite ---
describe('Resource Management', () => {
let myResource: Resource | null = null;
beforeEach(() => {
// Setup: Initialize a new resource before each test
myResource = new Resource('test-resource-id');
myResource.initialize();
});
afterEach(() => {
// Teardown: Release the resource after each test
if (myResource) {
myResource.release();
myResource = null; // Ensure it's reset for the next test
}
});
test('should initialize and release a resource', () => {
// This test will run with a fresh resource
expect(myResource).not.toBeNull();
expect(myResource!.isInitialized()).toBe(true);
// The release will happen in afterEach
});
});
Expected Output (Console Logs):
Initializing resource test-resource-id
Releasing resource test-resource-id
Explanation:
The beforeEach hook creates a Resource instance and calls initialize. The test then executes. Finally, the afterEach hook calls release on the same Resource instance.
Example 2: Multiple Tests
Consider a test suite with two tests.
// Using the same hypothetical Resource class as Example 1
// --- Jest Test Suite ---
describe('Resource Management with Multiple Tests', () => {
let myResource: Resource | null = null;
beforeEach(() => {
myResource = new Resource(`resource-${Math.random().toString(36).substring(7)}`);
myResource.initialize();
});
afterEach(() => {
if (myResource) {
myResource.release();
myResource = null;
}
});
test('test 1: resource is initialized', () => {
expect(myResource).not.toBeNull();
expect(myResource!.isInitialized()).toBe(true);
expect(myResource!.isReleased()).toBe(false); // Should not be released yet
});
test('test 2: resource is initialized and ready', () => {
expect(myResource).not.toBeNull();
expect(myResource!.isInitialized()).toBe(true);
expect(myResource!.isReleased()).toBe(false); // Should not be released yet
});
});
Expected Output (Console Logs - order may vary slightly for IDs):
Initializing resource <random_id_1>
Releasing resource <random_id_1>
Initializing resource <random_id_2>
Releasing resource <random_id_2>
Explanation:
Each test gets its own independent Resource instance. beforeEach sets it up, the test runs, and afterEach tears it down.
Constraints
- The simulated
Resourceclass should be a simple class within your test file or imported from a separate mock module. - The test suite should use standard Jest
describe,test,beforeEach, andafterEachfunctions. - You must use TypeScript for your solution.
- Avoid using any external libraries specifically designed for mocking lifecycle hooks; rely on Jest's built-in capabilities.
Notes
- Think about how to best track whether
initializeandreleaseare called, and which instance they operate on. You might use mock functions or add internal state to your simulatedResourceclass. - Consider the scenario where a resource might be initialized once for the entire suite (
beforeAll) and released once at the end (afterAll). While the primary focus isbeforeEach/afterEach, understandingbeforeAll/afterAllis relevant to resource management. - The goal is not to test the logic of the
Resourceclass itself, but rather to test that your Jest hooks are correctly orchestrating its lifecycle. - Success is defined by passing all Jest assertions that verify the correct invocation and state of the
initializeandreleasemethods.