Implementing a Mock Distributed Cache for Jest Tests
In modern application development, especially with microservices, distributed caching is a common pattern for improving performance and scalability. When testing services that rely on such caches, it's crucial to have a way to mock or simulate the behavior of the distributed cache without relying on a real, external cache instance during unit tests. This challenge focuses on building a simple, in-memory simulation of a distributed cache that can be easily integrated with Jest.
Problem Description
Your task is to implement a DistributedCache class in TypeScript that can be used to mock a distributed caching system within Jest tests. This class should simulate basic get and set operations. The "distributed" aspect will be simulated by allowing multiple instances of the DistributedCache class to share the same underlying data store, mimicking how a real distributed cache might operate across different nodes.
Key Requirements:
DistributedCacheClass: Create a TypeScript class namedDistributedCache.- Constructor: The constructor should accept an optional
storageKeystring. If provided, all instances created with the samestorageKeywill share the same underlying cache data. If nostorageKeyis provided, each instance will have its own independent cache. set(key: string, value: any): Promise<void>:- Accepts a string
keyand anyvalue. - Stores the
valueassociated with thekey. - Should return a
Promise<void>that resolves when the operation is complete.
- Accepts a string
get(key: string): Promise<any | undefined>:- Accepts a string
key. - Retrieves and returns the
valueassociated with thekey. - If the
keydoes not exist, it should returnundefined. - Should return a
Promise<any | undefined>that resolves with the value orundefined.
- Accepts a string
clear(): Promise<void>:- Removes all key-value pairs from the cache.
- Should return a
Promise<void>that resolves when the operation is complete.
- Shared Storage: Implement the sharing mechanism based on the
storageKeyprovided in the constructor. Use a static property within theDistributedCacheclass to hold shared data.
Expected Behavior:
- Multiple
DistributedCacheinstances with the samestorageKeyshould operate on the same set of cached data. - Instances with different
storageKeys (or nostorageKey) should have independent caches. - All operations should return Promises to simulate asynchronous behavior common in real-world cache interactions.
Edge Cases:
- Setting and getting values of various types (strings, numbers, objects, null, undefined).
- Getting a key that has not been set.
- Clearing a cache that is already empty.
Examples
Example 1: Independent Caches
// In your test file
import { DistributedCache } from './distributed-cache'; // Assuming your class is in this file
async function testIndependentCaches() {
const cache1 = new DistributedCache();
const cache2 = new DistributedCache();
await cache1.set('user1', { name: 'Alice', id: 1 });
const user1FromCache1 = await cache1.get('user1'); // Should be { name: 'Alice', id: 1 }
const user1FromCache2 = await cache2.get('user1'); // Should be undefined
console.log('user1FromCache1:', user1FromCache1);
console.log('user1FromCache2:', user1FromCache2);
await cache2.set('user2', { name: 'Bob', id: 2 });
const user2FromCache1 = await cache1.get('user2'); // Should be undefined
const user2FromCache2 = await cache2.get('user2'); // Should be { name: 'Bob', id: 2 }
console.log('user2FromCache1:', user2FromCache1);
console.log('user2FromCache2:', user2FromCache2);
}
// In a Jest test:
test('independent caches should not share data', async () => {
const cache1 = new DistributedCache();
const cache2 = new DistributedCache();
await cache1.set('greeting', 'hello');
expect(await cache1.get('greeting')).toBe('hello');
expect(await cache2.get('greeting')).toBeUndefined();
});
Example 2: Shared Caches
// In your test file
import { DistributedCache } from './distributed-cache';
async function testSharedCaches() {
const cacheA1 = new DistributedCache('session-abc');
const cacheA2 = new DistributedCache('session-abc');
const cacheB = new DistributedCache('session-xyz');
await cacheA1.set('token', 'xyz123');
const tokenFromA1 = await cacheA1.get('token'); // Should be 'xyz123'
const tokenFromA2 = await cacheA2.get('token'); // Should be 'xyz123'
const tokenFromB = await cacheB.get('token'); // Should be undefined
console.log('tokenFromA1:', tokenFromA1);
console.log('tokenFromA2:', tokenFromA2);
console.log('tokenFromB:', tokenFromB);
await cacheB.set('userCount', 100);
const countFromA1 = await cacheA1.get('userCount'); // Should be undefined
const countFromB = await cacheB.get('userCount'); // Should be 100
console.log('countFromA1:', countFromA1);
console.log('countFromB:', countFromB);
}
// In a Jest test:
test('shared caches with the same storageKey should share data', async () => {
const cacheA1 = new DistributedCache('shared-data');
const cacheA2 = new DistributedCache('shared-data');
const cacheB = new DistributedCache('different-data');
await cacheA1.set('config', { timeout: 5000 });
expect(await cacheA1.get('config')).toEqual({ timeout: 5000 });
expect(await cacheA2.get('config')).toEqual({ timeout: 5000 }); // Shared
expect(await cacheB.get('config')).toBeUndefined(); // Independent
});
Example 3: Clearing Cache
// In a Jest test:
test('clear should remove all items', async () => {
const cache = new DistributedCache('clear-test');
await cache.set('item1', 'value1');
await cache.set('item2', 'value2');
expect(await cache.get('item1')).toBe('value1');
expect(await cache.get('item2')).toBe('value2');
await cache.clear();
expect(await cache.get('item1')).toBeUndefined();
expect(await cache.get('item2')).toBeUndefined();
// Clearing an empty cache should not throw an error
await cache.clear();
expect(await cache.get('item1')).toBeUndefined(); // Still undefined
});
Constraints
- The
DistributedCacheclass must be implemented in TypeScript. - All
set,get, andclearmethods must return Promises. - The shared storage mechanism must be based on a static property of the
DistributedCacheclass. - The underlying data store for the cache can be a simple
Mapor plain JavaScript object. - There is no external network or actual distributed system involved; this is a simulation.
Notes
- Consider how you will manage the collection of shared caches. A
Mapwhere the key is thestorageKeyand the value is the actual cache data store would be a good approach. - Think about how to simulate the asynchronous nature of cache operations. Using
Promise.resolve()orsetTimeoutwithPromisecan achieve this. - This mock cache is intended for unit testing. Performance is not a primary concern, but the API should mimic that of a real distributed cache.
- When implementing the shared storage, ensure that each
storageKeycorrectly maps to its unique data store.