Unit Testing a Simple Cache with Jest
This challenge focuses on writing unit tests for a basic in-memory cache implementation using Jest in TypeScript. Writing robust tests is crucial for ensuring the reliability and correctness of any software component, and a cache is a common building block in many applications.
Problem Description
You are tasked with creating a comprehensive suite of unit tests for a simple Cache class. This class should support basic operations like set (to add or update a key-value pair), get (to retrieve a value by key), and delete (to remove a key-value pair). The cache should store data in memory and should also support an optional expiration time for entries.
Key Requirements:
set(key: string, value: any, ttl?: number): void:- Adds a new key-value pair to the cache.
- If the key already exists, its value and expiration time should be updated.
ttl(time-to-live) is an optional parameter in milliseconds. If provided, the entry should expire after this duration.
get(key: string): any | undefined:- Retrieves the value associated with the given key.
- If the key does not exist or has expired, it should return
undefined.
delete(key: string): boolean:- Removes the key-value pair from the cache.
- Returns
trueif the key was found and deleted,falseotherwise.
- Expiration Handling: Entries with a
ttlshould be automatically removed from the cache once they expire.
Expected Behavior:
- Successfully setting and getting a value should return the correct value.
- Attempting to get a non-existent key should return
undefined. - Deleting an existing key should remove it and return
true. - Deleting a non-existent key should return
false. - Expired entries should not be retrievable via
get.
Edge Cases to Consider:
- Setting and getting values of different data types.
- Setting a
ttlof 0 or a very smallttl. - Interactions between
set,get, anddeleteoperations. - Handling concurrent operations (though for this in-memory cache, this is less critical for basic unit tests, but good to keep in mind).
Examples
Example 1: Basic Set and Get
// Assume Cache class is implemented and imported
const cache = new Cache();
cache.set('user:1', { name: 'Alice' });
const user = cache.get('user:1'); // user should be { name: 'Alice' }
Explanation: A simple key-value pair is added and then retrieved successfully.
Example 2: Update and Delete
const cache = new Cache();
cache.set('count', 10);
console.log(cache.get('count')); // Output: 10
cache.set('count', 20); // Update the value
console.log(cache.get('count')); // Output: 20
const deleted = cache.delete('count'); // deleted should be true
console.log(deleted); // Output: true
console.log(cache.get('count')); // Output: undefined
Explanation: The value for 'count' is updated, and then the key is successfully deleted.
Example 3: Expiration
// Use Jest's fake timers for precise time control
jest.useFakeTimers();
const cache = new Cache();
cache.set('temp_data', 'sensitive_info', 500); // Expires in 500ms
console.log(cache.get('temp_data')); // Output: 'sensitive_info'
// Advance timers by less than the TTL
jest.advanceTimersByTime(400);
console.log(cache.get('temp_data')); // Output: 'sensitive_info'
// Advance timers beyond the TTL
jest.advanceTimersByTime(200); // Total advance: 600ms
console.log(cache.get('temp_data')); // Output: undefined
Explanation: An entry with a TTL is set. It remains accessible until the TTL passes, after which it becomes undefined.
Constraints
- The
Cacheclass will be implemented in TypeScript. - Your tests should be written using Jest.
- The
Cacheclass should not rely on external libraries for its core functionality (e.g., no external caching libraries). - Your tests should cover all the key requirements and edge cases mentioned above.
- Ensure tests are efficient and do not rely on excessively long waits (use fake timers for time-based tests).
Notes
- For tests involving expiration, leverage Jest's
useFakeTimers()andadvanceTimersByTime()to simulate the passage of time without actually waiting. Remember to clean up fake timers in your test setup/teardown. - Consider using
jest.spyOn()to mock or observe specific methods if needed, though for this straightforward implementation, direct assertions should suffice for most cases. - Think about how you will structure your tests for clarity and maintainability. Group related tests using
describeblocks.