Remote Cache with Jest Testing
This challenge focuses on building a simple remote cache using TypeScript and testing it thoroughly with Jest. Remote caching is a valuable technique for improving application performance by storing frequently accessed data on a remote server, reducing the load on the primary data source and enabling faster retrieval. You'll implement a basic cache that stores data in memory and retrieves it from a simulated remote API.
Problem Description
You are tasked with creating a RemoteCache class in TypeScript. This class should manage a cache of data fetched from a remote API. The cache should have the following functionalities:
get(key: string): Promise<any>: Retrieves data from the cache. If the data is present, it returns the cached value immediately. If not, it fetches the data from thefetchDatafunction (provided as a dependency), caches the result, and then returns the fetched data.clear(key: string): Promise<void>: Removes a specific key from the cache.fetchData(key: string): Promise<any>: A function that simulates fetching data from a remote API. This function should take a key as input and return a Promise that resolves with some data (you can hardcode this data for simplicity). This is not part of theRemoteCacheclass itself, but will be injected into it.
The RemoteCache class should use a simple in-memory object to store the cached data. The get method should handle potential errors from the fetchData function gracefully (e.g., by returning a default value or throwing an error).
Key Requirements:
- The cache should persist data across multiple calls to
getfor the same key. - The
fetchDatafunction should be mocked in your Jest tests. - The
clearmethod should successfully remove items from the cache. - The code should be well-structured and easy to understand.
Expected Behavior:
- The first time
getis called with a key, it should fetch data fromfetchDataand store it in the cache. - Subsequent calls to
getwith the same key should return the cached data without callingfetchDataagain. clearshould remove the key-value pair from the cache.getshould returnundefined(or a default value you choose) if the key is not in the cache andfetchDatafails.
Edge Cases to Consider:
- What happens if
fetchDatathrows an error? - How should the cache handle invalid keys? (For this challenge, you can assume keys are always valid strings.)
- Consider the potential for race conditions if multiple requests for the same key are made simultaneously (though a full solution to this is beyond the scope of this challenge).
Examples
Example 1:
Input:
fetchData = (key: string) => Promise.resolve({ data: 'initial data' });
cache = new RemoteCache(fetchData);
cache.get('myKey'); // Calls fetchData and caches the result
cache.get('myKey'); // Returns cached data immediately
Output:
First call: { data: 'initial data' }
Second call: { data: 'initial data' }
Explanation: The first call fetches the data and stores it. The second call retrieves it from the cache.
Example 2:
Input:
fetchData = (key: string) => Promise.resolve({ data: 'new data' });
cache = new RemoteCache(fetchData);
cache.get('myKey'); // Calls fetchData and caches the result
cache.clear('myKey');
cache.get('myKey'); // Calls fetchData again (cache is empty)
Output:
First call: { data: 'new data' }
Second call: { data: 'new data' }
Explanation: The first call fetches and caches. clear removes the entry. The second call fetches again because the cache is empty.
Example 3: (Error Handling)
Input:
fetchData = (key: string) => Promise.reject(new Error('API Error'));
cache = new RemoteCache(fetchData);
cache.get('myKey');
Output:
Error: API Error (or a default value if you handle the error)
Explanation: fetchData rejects, and the get method handles the error appropriately.
Constraints
- The cache should be implemented using a simple in-memory object (e.g.,
{[key: string]: any}). fetchDatashould be a function passed as a dependency to theRemoteCacheconstructor.- All asynchronous operations should be handled using Promises.
- The solution must be written in TypeScript.
- Jest tests must cover the core functionalities of the
RemoteCacheclass, including successful retrieval, caching, clearing, and error handling. - The solution should be reasonably performant for a simple in-memory cache. No complex caching algorithms are required.
Notes
- Focus on the core caching logic and testing. You don't need to implement a full-fledged remote API.
- Consider using
jest.fn()to mock thefetchDatafunction in your tests. - Think about how to structure your tests to effectively cover different scenarios and edge cases.
- Error handling is an important aspect of this challenge. Make sure your code handles potential errors gracefully.
- The
RemoteCacheclass should be designed to be reusable and testable.