References
In the last couple of months, I have been working extensively with TypeORM and built many projects using this ORM. Setting up a test database for my unit tests was quite challenging. To overcome this, I mocked TypeORM methods to avoid interacting with a real database. However, I found myself copying the same mock code into every new project repeatedly.
To streamline this process and help others facing the same issue, I decided to create a package. Initially, I mocked all the TypeORM code using Vitest, but that only worked in the Vitest environment. Since many developers use different testing frameworks such as Jest, Mocha, etc., I needed a more versatile solution. Hence, I used Sinon for mocking TypeORM, which is compatible with various testing frameworks. Although Sinon is excellent, I still have a soft spot for Vitest.
Installation
This package is built using TypeScript, so you’ll get type safety out of the box. I have also added thorough unit tests, which you can refer to for better understanding and reference.
To install this package, use the following command:
npm install --save-dev mock-typeorm sinon @types/sinon
That’s pretty much it! Now you can use this package to mock your TypeORM calls. Note that Sinon is added as a peer dependency, so you need to install it as well.
Key Concepts
Here’s how you can mock TypeORM calls. Use the following snippet:
import { MockTypeORM } from 'mock-typeorm'
test('abc', () => {
new MockTypeORM()
})
By just doing this, you prevent any interaction with your database. This is the magic of mocking.
Reset or Restore Mock State
After each test, you need to clear the mock state so that other tests cannot use the mock state of previous tests. Otherwise, you’ll get some strange results. To reset the state, you have different methods:
Method 1: Create an instance for each test
import Sinon from 'sinon'
import { MockTypeORM } from 'mock-typeorm'
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
describe('tests suite', () => {
let typeorm: MockTypeORM
beforeEach(() => {
typeorm = new MockTypeORM()
})
afterEach(() => {
typeorm.restore()
// you can also do this instead - Sinon.restore()
// Sinon.restore();
})
it('first test', async () => {
const mockUsers = ['user']
typeorm.onMock('User').toReturn(mockUsers, 'find')
const users = await dataSource.getRepository(User).find()
expect(users).toEqual(mockUsers)
})
it('second test', async () => {
const mockUsers = []
typeorm.onMock('User').toReturn(mockUsers, 'find')
const users = await dataSource.getRepository(User).find()
expect(users).toEqual(mockUsers)
})
})
In this approach, using hooks provided by Vitest (or similar hooks from other testing libraries), we create a new MockTypeORM object in the beforeEach hook and restore TypeORM to its original state in the afterEach hook.
Method 2: Single Instance
import Sinon from 'sinon'
import { MockTypeORM } from 'mock-typeorm'
import { afterAll, afterEach, beforeAll, describe, expect, it } from 'vitest'
describe('tests suite', () => {
let typeorm: MockTypeORM
beforeAll(() => {
typeorm = new MockTypeORM()
})
afterEach(() => {
typeorm.resetAll()
})
afterAll(() => {
typeorm.restore()
})
it('first test', async () => {
const mockUsers = ['user']
typeorm.onMock('User').toReturn(mockUsers, 'find')
const users = await dataSource.getRepository(User).find()
expect(users).toEqual(mockUsers)
})
it('second test', async () => {
const mockUsers = []
typeorm.onMock('User').toReturn(mockUsers, 'find')
const users = await dataSource.getRepository(User).find()
expect(users).toEqual(mockUsers)
})
})
In this approach, we create a MockTypeORM instance once before all tests start and reset the mock state after each test. After all tests, we restore TypeORM to its original behavior.
Mocking - Fun Stuff
Now that you understand how mocking works and how to restore it to its original behavior, let's see how to mock actual methods like find(), findOne(), save(), etc.
Example
describe('test suites', () => {
it('test', async () => {
const typeorm = new MockTypeORM()
typeorm.onMock(User).toReturn(['user'], 'find')
const userRepo = dataSource.getRepository(User)
const users = await userRepo.find()
expect(users).toEqual(['user'])
})
})
Helper Functions
- onMock()
- resetAll()
- restore()
onMock()
onMock() accepts a repository class or string (repository name). This is useful when using EntitySchema in JavaScript.
const typeorm = new MockTypeORM()
typeorm.onMock(User) // repository class
typeorm.onMock('User') // repository name as string
onMock() returns this to allow method chaining:
typeorm.onMock(User).toReturn([], 'find').toReturn({ id: '1' }, 'findOne')
reset()
onMock() also returns a reset() method to reset mock data:
describe('test suites', () => {
it('test', async () => {
const typeorm = new MockTypeORM()
typeorm.onMock(User).toReturn(['user'], 'find').reset('find')
const userRepo = dataSource.getRepository(User)
const users = await userRepo.find()
expect(users).toEqual({})
})
})
As you can see, I reset the mock data for the find method that we set using the toReturn() function. So when the find() method is called and no mock data is found for that method, it will return {} by default. That’s what we are expecting in our test assertion, meaning we have successfully reset the find method.
To reset everything at the repository level:
describe('test suites', () => {
it('test', async () => {
const typeorm = new MockTypeORM()
typeorm
.onMock(User)
.toReturn(['user'], 'find')
.toReturn({ id: '1' }, 'findOne')
.reset()
const userRepo = dataSource.getRepository(User)
const users = await userRepo.find()
const user = await userRepo.findOne({})
expect(users).toEqual({})
expect(user).toEqual({})
})
})
If you find this package useful please stare this my github repository and share with other developers.
So if you enjoy my work and found it useful, consider buying me a coffee! I would really appreciate it.
Top comments (0)