Моки для базы данных

Мок базы данных — это замена реального DB-клиента тестовым двойником (spy, mock, stub), который имитирует поведение репозитория без обращения к реальной базе данных, что позволяет тестировать бизнес-логику быстро и изолированно.

Зачем нужно

Unit-тесты должны быть быстрыми и не зависеть от внешних ресурсов. Тест, который обращается к реальной PostgreSQL, требует запущенного сервера, чистой схемы и данных — это медленно и ненадёжно. Мокирование репозитория позволяет тестировать Service-слой изолированно: проверять логику, не трогая БД.

Где используется

  • Unit-тесты Service-слоя (UsersService, OrdersService)
  • Тесты с непредсказуемыми данными (проверка граничных случаев)
  • CI без доступа к реальной БД
  • TDD — писать тесты до реализации Repository

Основной контент

Мок через jest.mock (Repository)

// services/UsersService.js
const UsersRepository = require('../repositories/UsersRepository');

class UsersService {
  async getById(id) {
    const user = await UsersRepository.findById(id);
    if (!user) throw new Error('User not found');
    return user;
  }

  async create({ name, email }) {
    const existing = await UsersRepository.findByEmail(email);
    if (existing) throw new Error('Email already in use');
    return UsersRepository.create({ name, email });
  }
}
module.exports = new UsersService();
// __tests__/UsersService.test.js
jest.mock('../repositories/UsersRepository');
const UsersRepository = require('../repositories/UsersRepository');
const UsersService = require('../services/UsersService');

describe('UsersService.getById', () => {
  it('возвращает пользователя', async () => {
    const mockUser = { id: 1, name: 'Alice', email: 'alice@example.com' };
    UsersRepository.findById.mockResolvedValue(mockUser);

    const user = await UsersService.getById(1);
    expect(user).toEqual(mockUser);
    expect(UsersRepository.findById).toHaveBeenCalledWith(1);
  });

  it('бросает ошибку если пользователь не найден', async () => {
    UsersRepository.findById.mockResolvedValue(null);
    await expect(UsersService.getById(99)).rejects.toThrow('User not found');
  });
});

describe('UsersService.create', () => {
  it('не создаёт при дублировании email', async () => {
    UsersRepository.findByEmail.mockResolvedValue({ id: 1 }); // уже существует
    await expect(UsersService.create({ name: 'Bob', email: 'alice@example.com' }))
      .rejects.toThrow('Email already in use');
    expect(UsersRepository.create).not.toHaveBeenCalled();
  });
});

Мок Prisma Client

// __mocks__/@prisma/client.js (автомок)
const prismaMock = {
  user: {
    findUnique: jest.fn,
    findMany: jest.fn,
    create: jest.fn,
    update: jest.fn,
    delete: jest.fn
  },
  $transaction: jest.fn((fn) => fn(prismaMock))
};

module.exports = { PrismaClient: jest.fn( => prismaMock) };
// В тесте
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();

prisma.user.findUnique.mockResolvedValue({ id: 1, email: 'alice@example.com' });

mongodb-memory-server — in-memory MongoDB

npm install --save-dev mongodb-memory-server
// jest.setup.js
const { MongoMemoryServer } = require('mongodb-memory-server');
const mongoose = require('mongoose');

let mongod;

beforeAll(async () => {
  mongod = await MongoMemoryServer.create;
  await mongoose.connect(mongod.getUri);
});

afterEach(async () => {
  // Очистить все коллекции
  const collections = await mongoose.connection.db.collections;
  for (const collection of collections) {
    await collection.deleteMany({});
  }
});

afterAll(async () => {
  await mongoose.disconnect();
  await mongod.stop();
});
// В тесте — реальные Mongoose-операции, но in-memory
const User = require('../models/User');

it('создаёт пользователя', async () => {
  const user = await User.create({ name: 'Alice', email: 'alice@example.com' });
  expect(user._id).toBeDefined();
  expect(user.name).toBe('Alice');
});

Мок через Dependency Injection

// Если Service принимает Repository через конструктор — мокировать проще
class UsersService {
  constructor(usersRepository) {
    this.repo = usersRepository;
  }

  async getById(id) {
    const user = await this.repo.findById(id);
    if (!user) throw new Error('Not found');
    return user;
  }
}

// В тесте
const mockRepo = { findById: jest.fn };
const service = new UsersService(mockRepo);

mockRepo.findById.mockResolvedValue({ id: 1, name: 'Alice' });
const user = await service.getById(1);

Частые ошибки

  • Мокировать то, чем не владеешь — не мокировать pg, mongoose напрямую; мокировать свой Repository
  • Не сбрасывать моки между тестамиjest.clearAllMocks или clearMocks: true в jest.config
  • Тестировать только успешные случаи — всегда мокировать null, Error, граничные значения
  • Мокировать в интеграционных тестах — интеграционные тесты должны использовать реальную (тестовую) БД

Связанные темы

Ресурсы