Hone logo
Hone
Problems

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:

  • DistributedCache Class: Create a TypeScript class named DistributedCache.
  • Constructor: The constructor should accept an optional storageKey string. If provided, all instances created with the same storageKey will share the same underlying cache data. If no storageKey is provided, each instance will have its own independent cache.
  • set(key: string, value: any): Promise<void>:
    • Accepts a string key and any value.
    • Stores the value associated with the key.
    • Should return a Promise<void> that resolves when the operation is complete.
  • get(key: string): Promise<any | undefined>:
    • Accepts a string key.
    • Retrieves and returns the value associated with the key.
    • If the key does not exist, it should return undefined.
    • Should return a Promise<any | undefined> that resolves with the value or undefined.
  • 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 storageKey provided in the constructor. Use a static property within the DistributedCache class to hold shared data.

Expected Behavior:

  • Multiple DistributedCache instances with the same storageKey should operate on the same set of cached data.
  • Instances with different storageKeys (or no storageKey) 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 DistributedCache class must be implemented in TypeScript.
  • All set, get, and clear methods must return Promises.
  • The shared storage mechanism must be based on a static property of the DistributedCache class.
  • The underlying data store for the cache can be a simple Map or 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 Map where the key is the storageKey and 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() or setTimeout with Promise can 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 storageKey correctly maps to its unique data store.
Loading editor...
typescript