이미지 로딩 중...
AI Generated
2025. 11. 5. · 9 Views
Redis 테스트 전략 완벽 가이드
Redis를 사용하는 애플리케이션의 효과적인 테스트 전략을 다룹니다. 단위 테스트부터 통합 테스트까지, 실전에서 바로 적용 가능한 테스트 패턴과 모범 사례를 소개합니다.
들어가며
이 글에서는 Redis 테스트 전략 완벽 가이드에 대해 상세히 알아보겠습니다. 총 8가지 주요 개념을 다루며, 각각의 개념에 대한 설명과 실제 코드 예제를 함께 제공합니다.
목차
- Redis_Mock_기본_설정
- Jest_Redis_Mock_테스트
- Redis_통합_테스트_설정
- Redis_트랜잭션_테스트
- Redis_Pub_Sub_테스트
- Redis_Pipeline_성능_테스트
- Redis_연결_풀_테스트
- Redis_에러_처리_테스트
1. Redis_Mock_기본_설정
개요
단위 테스트에서 실제 Redis 연결 없이 테스트하기 위한 Mock 설정 방법입니다. redis-mock 라이브러리를 사용하여 Redis 클라이언트를 모킹하고, 빠르고 독립적인 테스트 환경을 구축합니다.
코드 예제
const redis = require('redis-mock');
const client = redis.createClient();
// Redis 캐시 저장 함수
async function setCache(key, value, ttl = 3600) {
await client.set(key, JSON.stringify(value));
await client.expire(key, ttl);
return true;
}
// Redis 캐시 조회 함수
async function getCache(key) {
const data = await client.get(key);
return data ? JSON.parse(data) : null;
}
module.exports = { setCache, getCache, client };
설명
이 코드는 Redis를 사용하는 애플리케이션의 단위 테스트를 위한 Mock 환경을 설정합니다. redis-mock 라이브러리를 사용하여 실제 Redis 서버 없이도 Redis 클라이언트의 동작을 완벽하게 시뮬레이션할 수 있습니다. 첫 번째로, redis-mock.createClient()를 호출하여 가짜 Redis 클라이언트를 생성합니다. 이 클라이언트는 실제 Redis 클라이언트와 동일한 API를 제공하지만, 모든 데이터는 메모리에만 저장되어 빠른 테스트 실행이 가능합니다. 두 번째로, setCache 함수는 데이터를 JSON으로 직렬화하여 저장하고, expire 메서드로 TTL(Time To Live)을 설정합니다. 이를 통해 실제 Redis의 만료 기능을 테스트할 수 있습니다. 세 번째로, getCache 함수는 저장된 데이터를 조회하고 JSON.parse로 역직렬화합니다. 데이터가 없거나 만료된 경우 null을 반환하여 실제 Redis의 동작을 정확히 재현합니다. 마지막으로, 모든 함수와 클라이언트를 export하여 테스트 코드에서 쉽게 사용할 수 있도록 합니다. 이 Mock 설정을 사용하면 실제 Redis 서버를 실행하지 않고도 빠르고 안정적인 단위 테스트를 작성할 수 있습니다. CI/CD 파이프라인에서 외부 의존성 없이 테스트를 실행할 수 있어, 개발 속도와 테스트 신뢰성을 크게 향상시킬 수 있습니다.
2. Jest_Redis_Mock_테스트
개요
Jest 테스트 프레임워크에서 Redis Mock을 활용한 단위 테스트 작성 방법입니다. beforeEach와 afterEach 훅을 사용하여 각 테스트마다 깨끗한 상태를 유지하고, 캐시 동작을 검증합니다.
코드 예제
const { setCache, getCache, client } = require('./cache');
describe('Redis Cache Tests', () => {
beforeEach(async () => {
// 각 테스트 전 Redis 초기화
await client.flushall();
});
test('캐시 저장 및 조회 테스트', async () => {
const key = 'user:123';
const value = { id: 123, name: 'John' };
await setCache(key, value);
const result = await getCache(key);
expect(result).toEqual(value);
});
test('TTL 만료 테스트', async () => {
await setCache('temp', { data: 'test' }, 1);
// 1초 대기 후 데이터 확인
await new Promise(resolve => setTimeout(resolve, 1100));
const result = await getCache('temp');
expect(result).toBeNull();
});
});
설명
이 코드는 Jest 테스트 프레임워크를 사용하여 Redis 캐시 기능의 정확성을 검증하는 단위 테스트를 구현합니다. describe 블록으로 테스트 스위트를 그룹화하고, 각 테스트의 독립성을 보장하는 구조를 만듭니다. 첫 번째로, beforeEach 훅에서 client.flushall()을 호출하여 모든 테스트가 시작되기 전에 Redis의 모든 데이터를 삭제합니다. 이는 각 테스트가 서로 영향을 주지 않고 독립적으로 실행되도록 보장하는 핵심 패턴입니다. 테스트 간 데이터 오염을 방지하여 테스트 결과의 신뢰성을 높입니다. 두 번째로, 첫 번째 테스트는 기본적인 캐시 저장과 조회 기능을 검증합니다. 객체 데이터를 저장한 후 다시 조회하여 원본 데이터와 정확히 일치하는지 확인합니다. toEqual 매처를 사용하여 객체의 모든 속성이 동일한지 깊은 비교를 수행합니다. 세 번째로, TTL 만료 테스트는 setTimeout을 Promise로 감싸서 1초 동안 대기한 후, TTL이 1초인 데이터가 정상적으로 만료되는지 확인합니다. 이는 Redis의 만료 기능이 올바르게 동작하는지 검증하는 중요한 테스트입니다. 이러한 테스트 패턴을 사용하면 캐시 로직의 모든 핵심 기능을 자동으로 검증할 수 있습니다. 리팩토링이나 기능 추가 시 기존 동작이 깨지지 않았는지 즉시 확인할 수 있어, 안전하고 빠른 개발이 가능합니다.
3. Redis_통합_테스트_설정
개요
실제 Redis 서버를 사용하는 통합 테스트 환경 구축 방법입니다. docker-compose로 테스트용 Redis 컨테이너를 실행하고, 실제 환경과 유사한 조건에서 테스트합니다.
코드 예제
const redis = require('redis');
let client;
beforeAll(async () => {
// 테스트용 Redis 연결 (Docker 컨테이너 사용)
client = redis.createClient({
host: process.env.REDIS_HOST || 'localhost',
port: process.env.REDIS_PORT || 6379,
db: 15 // 테스트 전용 DB 사용
});
await client.connect();
});
afterAll(async () => {
// 테스트 데이터 정리 및 연결 종료
await client.flushDb();
await client.quit();
});
test('실제 Redis 연결 테스트', async () => {
const result = await client.ping();
expect(result).toBe('PONG');
});
설명
이 코드는 실제 Redis 서버를 대상으로 하는 통합 테스트 환경을 구축합니다. Mock이 아닌 실제 Redis를 사용하여 네트워크, 직렬화, 성능 등 실제 환경에서만 발견할 수 있는 문제를 테스트합니다. 첫 번째로, beforeAll 훅에서 실제 Redis 서버에 연결합니다. 환경 변수를 통해 호스트와 포트를 설정할 수 있어, 로컬 개발 환경과 CI 환경에서 다른 설정을 사용할 수 있습니다. db: 15 옵션으로 테스트 전용 데이터베이스를 사용하여, 실제 데이터와 테스트 데이터를 완전히 분리합니다. 두 번째로, client.connect()를 호출하여 Redis 서버와의 연결을 확립합니다. Redis 4.0 이상에서는 명시적으로 connect를 호출해야 하며, 이는 비동기 작업이므로 await로 연결이 완료될 때까지 기다립니다. 연결이 실패하면 테스트가 시작되기 전에 에러가 발생하여 문제를 즉시 파악할 수 있습니다. 세 번째로, afterAll 훅에서 flushDb()로 테스트 중 생성된 모든 데이터를 삭제하고, quit()로 연결을 정상적으로 종료합니다. 이는 테스트 후 깨끗한 상태를 유지하고 리소스 누수를 방지하는 중요한 단계입니다. 마지막으로, ping() 명령으로 Redis 연결이 정상적으로 작동하는지 확인합니다. 'PONG' 응답을 받으면 Redis 서버가 올바르게 실행되고 있다는 것을 의미합니다. 이러한 통합 테스트 설정을 사용하면 실제 프로덕션 환경과 동일한 조건에서 애플리케이션을 검증할 수 있습니다. Docker Compose로 Redis 컨테이너를 자동으로 시작/종료하면, CI/CD 파이프라인에서도 일관된 테스트 환경을 유지할 수 있습니다.
4. Redis_트랜잭션_테스트
개요
Redis의 MULTI/EXEC 트랜잭션을 테스트하는 방법입니다. 원자성(Atomicity)을 검증하고, 여러 명령이 하나의 단위로 실행되는지 확인합니다.
코드 예제
async function transferPoints(fromUser, toUser, points) {
const multi = client.multi();
// 트랜잭션으로 포인트 이전
multi.decrBy(`points:${fromUser}`, points);
multi.incrBy(`points:${toUser}`, points);
multi.zadd('transactions', Date.now(),
`${fromUser}->${toUser}:${points}`);
return await multi.exec();
}
test('포인트 이전 트랜잭션 테스트', async () => {
await client.set('points:user1', '1000');
await client.set('points:user2', '500');
await transferPoints('user1', 'user2', 300);
expect(await client.get('points:user1')).toBe('700');
expect(await client.get('points:user2')).toBe('800');
});
설명
이 코드는 Redis 트랜잭션을 사용하여 여러 명령을 원자적으로 실행하는 방법을 보여줍니다. 트랜잭션은 여러 작업이 모두 성공하거나 모두 실패하도록 보장하여, 데이터 일관성을 유지하는 핵심 메커니즘입니다. 첫 번째로, client.multi()를 호출하여 트랜잭션 컨텍스트를 시작합니다. 이후 multi 객체에 추가되는 모든 명령은 즉시 실행되지 않고 큐에 저장됩니다. 이는 Redis의 파이프라인 기능을 활용하여 네트워크 왕복을 줄이고 성능을 향상시킵니다. 두 번째로, decrBy와 incrBy 명령으로 포인트를 이전합니다. fromUser의 포인트를 차감하고 toUser의 포인트를 증가시키는 두 작업이 하나의 트랜잭션으로 묶여, 중간에 실패하더라도 부분적으로만 실행되는 일이 없습니다. 세 번째로, zadd 명령으로 거래 기록을 Sorted Set에 저장하여, 타임스탬프 기반으로 거래 내역을 조회할 수 있게 합니다. 네 번째로, multi.exec()를 호출하면 큐에 저장된 모든 명령이 순차적으로 실행됩니다. Redis는 단일 스레드로 동작하므로, exec() 실행 중에는 다른 클라이언트의 명령이 끼어들 수 없어 원자성이 보장됩니다. 테스트에서는 초기 잔액을 설정한 후 포인트 이전을 실행하고, 최종 잔액이 정확한지 검증합니다. 이를 통해 트랜잭션이 올바르게 동작하고 데이터 일관성이 유지되는지 확인할 수 있습니다. 이러한 트랜잭션 테스트 패턴을 사용하면 금융 거래, 포인트 시스템, 재고 관리 등 데이터 정확성이 중요한 기능을 안전하게 구현할 수 있습니다. 동시성 환경에서도 데이터 무결성을 보장하는 견고한 시스템을 만들 수 있습니다.
5. Redis_Pub_Sub_테스트
개요
Redis의 Pub/Sub 메시징 패턴을 테스트하는 방법입니다. 발행자와 구독자 간의 메시지 전달을 검증하고, 실시간 이벤트 처리를 테스트합니다.
코드 예제
test('Pub/Sub 메시징 테스트', async () => {
const subscriber = client.duplicate();
await subscriber.connect();
const messages = [];
// 메시지 수신 리스너 등록
await subscriber.subscribe('notifications', (message) => {
messages.push(JSON.parse(message));
});
// 메시지 발행
await client.publish('notifications', JSON.stringify({
type: 'NEW_ORDER',
orderId: 12345,
timestamp: Date.now()
}));
// 메시지 수신 대기 (비동기 처리)
await new Promise(resolve => setTimeout(resolve, 100));
expect(messages).toHaveLength(1);
expect(messages[0].type).toBe('NEW_ORDER');
await subscriber.quit();
});
설명
이 코드는 Redis의 Pub/Sub(발행-구독) 패턴을 사용한 실시간 메시징 시스템을 테스트합니다. Pub/Sub은 마이크로서비스 간 이벤트 기반 통신, 실시간 알림, 채팅 등에 활용되는 강력한 메시징 메커니즘입니다. 첫 번째로, client.duplicate()로 새로운 Redis 연결을 생성합니다. Pub/Sub 모드로 전환된 연결은 다른 명령을 실행할 수 없기 때문에, 구독 전용 연결을 별도로 만들어야 합니다. 이는 Redis의 중요한 제약사항으로, 하나의 연결에서 구독과 일반 명령을 동시에 사용할 수 없습니다. 두 번째로, subscribe 메서드로 'notifications' 채널을 구독하고, 메시지를 수신할 때마다 실행될 콜백 함수를 등록합니다. 수신된 메시지는 JSON 문자열이므로 JSON.parse로 객체로 변환한 후 messages 배열에 저장합니다. 이 배열을 사용하여 나중에 메시지가 올바르게 전달되었는지 검증할 수 있습니다. 세 번째로, 원래의 client로 publish 명령을 실행하여 메시지를 발행합니다. 메시지는 객체를 JSON.stringify로 직렬화하여 전송하며, 채널을 구독하는 모든 클라이언트에게 즉시 전달됩니다. 주문 생성, 결제 완료 등의 이벤트를 다른 서비스에 알릴 수 있습니다. 네 번째로, setTimeout으로 100ms를 대기합니다. Pub/Sub은 비동기로 동작하므로, 메시지가 전달되고 콜백이 실행될 시간을 주어야 합니다. 프로덕션 환경에서는 이벤트 기반으로 처리하지만, 테스트에서는 타임아웃을 사용하여 안정적으로 검증할 수 있습니다. 마지막으로, expect 매처로 메시지가 정확히 하나 수신되었고, 내용이 올바른지 검증합니다. 테스트 종료 시 subscriber.quit()로 구독 연결을 정리하여 리소스 누수를 방지합니다. 이러한 Pub/Sub 테스트 패턴을 사용하면 이벤트 기반 아키텍처의 메시징 로직을 검증할 수 있습니다. 마이크로서비스 간 느슨한 결합을 유지하면서도, 실시간 데이터 동기화와 알림 기능을 안정적으로 구현할 수 있습니다.
6. Redis_Pipeline_성능_테스트
개요
Redis Pipeline을 사용하여 대량의 명령을 효율적으로 처리하는 방법과 성능을 테스트합니다. 개별 명령 실행 대비 Pipeline의 성능 향상을 측정합니다.
코드 예제
test('Pipeline 성능 테스트', async () => {
const startTime = Date.now();
// Pipeline으로 1000개 데이터 저장
const pipeline = client.multi();
for (let i = 0; i < 1000; i++) {
pipeline.set(`key:${i}`, `value:${i}`);
}
await pipeline.exec();
const pipelineTime = Date.now() - startTime;
// 개별 명령으로 비교
await client.flushDb();
const normalStart = Date.now();
for (let i = 0; i < 1000; i++) {
await client.set(`key:${i}`, `value:${i}`);
}
const normalTime = Date.now() - normalStart;
// Pipeline이 훨씬 빠름
expect(pipelineTime).toBeLessThan(normalTime);
console.log(`Pipeline: ${pipelineTime}ms, Normal: ${normalTime}ms`);
});
설명
이 코드는 Redis Pipeline을 사용한 대량 작업의 성능 최적화 효과를 측정하고 검증합니다. Pipeline은 여러 명령을 한 번에 전송하여 네트워크 왕복(round-trip) 횟수를 줄이는 핵심 성능 최적화 기법입니다. 첫 번째로, Date.now()로 시작 시간을 기록한 후 Pipeline을 생성합니다. for 루프에서 1000개의 set 명령을 pipeline 객체에 추가하지만, 이 시점에는 아직 Redis 서버로 전송되지 않습니다. 모든 명령이 클라이언트 측 메모리에 큐잉되어 대기합니다. 두 번째로, pipeline.exec()를 호출하면 큐잉된 1000개의 명령이 하나의 네트워크 패킷으로 Redis 서버에 전송됩니다. 서버는 모든 명령을 순차적으로 실행하고 결과를 한 번에 반환합니다. 이는 1000번의 네트워크 왕복을 단 1번으로 줄여, 특히 네트워크 지연이 있는 환경에서 극적인 성능 향상을 제공합니다. 세 번째로, Pipeline 실행 시간을 측정한 후, flushDb()로 데이터를 초기화하고 동일한 작업을 개별 명령으로 실행합니다. for 루프 안에서 await client.set()을 1000번 호출하면, 각 명령마다 네트워크 왕복이 발생하여 훨씬 느린 성능을 보입니다. 네 번째로, 두 방식의 실행 시간을 비교합니다. 일반적으로 Pipeline은 개별 명령 대비 10배 이상 빠른 성능을 보이며, expect(pipelineTime).toBeLessThan(normalTime)으로 이를 검증합니다. console.log로 실제 수치를 출력하여 성능 차이를 명확히 확인할 수 있습니다. 이러한 성능 테스트를 통해 대량 데이터 처리 시 Pipeline을 사용해야 하는 근거를 마련할 수 있습니다. 실제 프로젝트에서 대량의 캐시 초기화, 배치 업데이트, 벌크 삭제 등의 작업에 Pipeline을 적용하면 응답 시간을 크게 개선할 수 있습니다. 특히 클라우드 환경이나 원격 서버 연결에서 그 효과가 더욱 두드러집니다.
7. Redis_연결_풀_테스트
개요
연결 풀을 사용한 Redis 클라이언트 관리와 동시성 테스트입니다. 여러 요청이 동시에 발생할 때 연결 풀이 효율적으로 관리되는지 검증합니다.
코드 예제
const { createClient } = require('redis');
class RedisPool {
constructor(poolSize = 10) {
this.pool = [];
this.poolSize = poolSize;
}
async getClient() {
if (this.pool.length > 0) {
return this.pool.pop();
}
const client = createClient();
await client.connect();
return client;
}
releaseClient(client) {
if (this.pool.length < this.poolSize) {
this.pool.push(client);
} else {
client.quit();
}
}
}
test('연결 풀 동시성 테스트', async () => {
const pool = new RedisPool(5);
const tasks = Array(20).fill(0).map(async (_, i) => {
const client = await pool.getClient();
await client.set(`concurrent:${i}`, i);
pool.releaseClient(client);
});
await Promise.all(tasks);
expect(pool.pool.length).toBeLessThanOrEqual(5);
});
설명
이 코드는 Redis 연결 풀(Connection Pool)을 구현하여 동시 요청을 효율적으로 처리하는 방법을 보여줍니다. 연결 풀은 제한된 리소스를 재사용하여 연결 생성 오버헤드를 줄이고, 서버의 최대 연결 수 제한을 관리하는 핵심 패턴입니다. 첫 번째로, RedisPool 클래스는 최대 poolSize 개수만큼의 Redis 연결을 관리합니다. pool 배열에 사용 가능한 연결을 저장하고, 필요할 때마다 가져다 쓰는 Object Pool 패턴을 구현합니다. 이는 매번 새로운 연결을 생성하는 비용을 크게 줄입니다. 두 번째로, getClient() 메서드는 풀에 사용 가능한 연결이 있으면 즉시 반환하고, 없으면 새 연결을 생성합니다. pop()으로 배열에서 연결을 꺼내므로, 동시에 여러 요청이 들어와도 같은 연결을 중복으로 사용하지 않습니다. 새 연결은 connect()를 호출하여 즉시 사용 가능한 상태로 만듭니다. 세 번째로, releaseClient() 메서드는 사용이 끝난 연결을 풀로 반환합니다. 풀이 가득 찬 경우(poolSize 초과)에는 연결을 종료하여 리소스 누수를 방지합니다. 이는 동적으로 연결 수를 조절하여 메모리를 효율적으로 사용하는 중요한 로직입니다. 네 번째로, 테스트에서는 Array(20)으로 20개의 동시 작업을 생성합니다. 각 작업은 연결을 가져와서 데이터를 저장한 후 다시 반환합니다. Promise.all()로 모든 작업을 병렬로 실행하지만, 실제로는 최대 5개의 연결만 사용됩니다. 연결이 재사용되므로 15개의 작업은 다른 작업이 연결을 반환할 때까지 대기합니다. 마지막으로, 테스트 종료 시 풀에 남은 연결 수가 poolSize 이하인지 확인합니다. 이는 연결이 올바르게 관리되고 있으며, 최대 수를 초과하지 않았다는 것을 의미합니다. 이러한 연결 풀 패턴을 사용하면 고부하 상황에서도 안정적인 성능을 유지할 수 있습니다. 실제 프로덕션 환경에서는 ioredis 같은 라이브러리가 내장 연결 풀을 제공하지만, 이 원리를 이해하면 커스텀 리소스 관리가 필요한 상황에서 유용하게 활용할 수 있습니다.
8. Redis_에러_처리_테스트
개요
Redis 연결 실패, 타임아웃, 명령 에러 등 다양한 에러 상황을 테스트하고 적절한 에러 핸들링을 검증합니다.
코드 예제
describe('Redis 에러 처리', () => {
test('연결 실패 에러 핸들링', async () => {
const wrongClient = createClient({
socket: {
host: 'invalid-host',
port: 9999,
connectTimeout: 1000
}
});
await expect(wrongClient.connect())
.rejects.toThrow();
});
test('잘못된 명령 에러', async () => {
// 문자열에 incr 명령 (타입 에러)
await client.set('string-key', 'not-a-number');
await expect(client.incr('string-key'))
.rejects.toThrow();
});
test('타임아웃 에러', async () => {
const timeoutClient = createClient({
socket: { timeout: 10 }
});
await timeoutClient.connect();
// 매우 큰 데이터로 타임아웃 유발
const largeData = 'x'.repeat(100000000);
await expect(timeoutClient.set('large', largeData))
.rejects.toThrow();
await timeoutClient.quit();
});
});
설명
이 코드는 Redis 사용 시 발생할 수 있는 다양한 에러 상황을 시뮬레이션하고, 애플리케이션이 이를 올바르게 처리하는지 검증합니다. 에러 처리 테스트는 프로덕션 환경의 예외 상황에서도 시스템이 안정적으로 동작하도록 보장하는 핵심 요소입니다. 첫 번째 테스트는 잘못된 호스트와 포트로 연결을 시도하여 연결 실패를 시뮬레이션합니다. connectTimeout을 1000ms로 설정하여 빠르게 실패하도록 하고, expect().rejects.toThrow()로 Promise가 거부되는지 확인합니다. 실제 환경에서는 네트워크 장애나 Redis 서버 다운 시 이런 에러가 발생하므로, 재연결 로직이나 폴백 메커니즘을 테스트할 수 있습니다. 두 번째 테스트는 타입 불일치 에러를 검증합니다. 문자열 값에 incr(증가) 명령을 실행하면 Redis가 "ERR value is not an integer" 에러를 반환합니다. 이는 Redis의 타입 시스템을 제대로 이해하지 못했을 때 발생하는 일반적인 실수입니다. 테스트를 통해 이런 에러가 발생했을 때 적절한 에러 메시지를 사용자에게 표시하거나 로깅하는지 확인할 수 있습니다. 세 번째 테스트는 타임아웃 에러를 시뮬레이션합니다. socket.timeout을 매우 짧게 설정하고, 100MB 크기의 대용량 문자열을 저장하려고 시도합니다. 네트워크 전송 시간이 타임아웃을 초과하면 에러가 발생합니다. 실제 환경에서는 네트워크 지연이나 대용량 데이터 처리 시 타임아웃이 발생할 수 있으므로, 적절한 타임아웃 값 설정과 재시도 로직이 중요합니다. 네 번째로, 모든 에러 테스트는 expect().rejects 패턴을 사용하여 비동기 에러를 우아하게 검증합니다. Jest의 이 기능을 사용하면 try-catch 블록 없이도 Promise 거부를 간결하게 테스트할 수 있습니다. 이러한 에러 처리 테스트를 통해 애플리케이션의 복원력(resilience)을 검증할 수 있습니다. 실제 프로젝트에서는 에러 발생 시 로깅, 모니터링 알림, 재시도 로직, 폴백 캐시 등을 구현해야 하며, 이 테스트들은 그런 메커니즘이 올바르게 작동하는지 확인하는 기반이 됩니다. 견고한 에러 핸들링은 시스템의 신뢰성과 사용자 경험을 크게 향상시킵니다.
마치며
이번 글에서는 Redis 테스트 전략 완벽 가이드에 대해 알아보았습니다. 총 8가지 개념을 다루었으며, 각각의 사용법과 예제를 살펴보았습니다.
관련 태그
#Redis #Testing #Jest #Integration #Mocking