Cannot read property '_transformer' of undefined
liangchunn opened this issue · comments
Description
I cannot use ioredis-mock
with Jest module mocking, it keeps erroring back the below error. I've tried to pinpoint it down but couldn't find anything. Any ideas?
FAIL src/__tests__/Redis.ts
● Test suite failed to run
TypeError: Cannot read property '_transformer' of undefined
> 1 | const ioredis = require('ioredis-mock')
| ^
2 | module.exports = ioredis
at Object.<anonymous> (node_modules/ioredis-mock/lib/index.js:148:34)
at Object.<anonymous> (__mocks__/ioredis.ts:1:44)
at Object.<anonymous> (src/Redis.ts:35:17)
Source files
/__mocks__/ioredis.ts
const ioredis = require('ioredis-mock')
module.exports = ioredis
/src/__tests__/Redis.ts
import { Redis } from "../Redis";
// uncommenting the line below will require the actual ioredis
// jest.unmock('ioredis')
describe('RedisAdapter', () => {
it('can get', async () => {
const redis = new Redis();
expect(await redis.get('KEY')).toBe(null)
})
})
/src/Redis.ts
import * as ioredis from 'ioredis'
export class Redis {
private instance: ioredis.Redis | null
constructor() {
this.instance = null
}
private async getInstance() {
if (!this.instance) {
this.instance = new ioredis({
host: '127.0.0.1',
port: 6379,
retryStrategy: (times) => false
})
return this.instance
} else {
return this.instance
}
}
public async get(k: string) {
return this.getInstance().then(instance => instance.get(k))
}
}
Sample Repo
Hey!
This is because ioredis-mock
is importing some code from ioredis
.
Thus for jest mocking to work I recommend you put create another small module that imports ioredis
so that you can mock that module by path. Allowing ioredis-mock
to import code from ioredis
without jest's mocking system intervening 😄
Something like:
/src/__mocks__/RedisClient.ts
const ioredis = require('ioredis-mock')
module.exports = ioredis
/src/__tests__/Redis.ts
import { Redis } from "../Redis";
describe('RedisAdapter', () => {
it('can get', async () => {
const redis = new Redis();
expect(await redis.get('KEY')).toBe(null)
})
})
/src/RedisClient.ts
import * as ioredis from 'ioredis'
export default redis
/src/Redis.ts
import ioredis from './RedisClient'
export class Redis {
private instance: ioredis.Redis | null
constructor() {
this.instance = null
}
private async getInstance() {
if (!this.instance) {
this.instance = new ioredis({
host: '127.0.0.1',
port: 6379,
retryStrategy: (times) => false
})
return this.instance
} else {
return this.instance
}
}
public async get(k: string) {
return this.getInstance().then(instance => instance.get(k))
}
}
Hope this helps 😄
@stipsan Thank you so much, it worked! Updating the example repo just in case anybody stumbles across this issue.
Great to hear! 👍
For what it worth, if you don't want to change to introduce this client file, here is what you can do:
jest.mock('ioredis', () => {
const Redis = require('ioredis-mock')
if (typeof Redis === 'object') {
// the first mock is an ioredis shim because ioredis-mock depends on it
// https://github.com/stipsan/ioredis-mock/blob/master/src/index.js#L101-L111
return {
Command: { _transformer: { argument: {}, reply: {} } }
}
}
// second mock for our code
return function(...args) {
return new Redis(args)
}
})
If you need access to the ioredis-mock instance:
const mocks = { redis: null }
jest.mock('ioredis', () => {
const Redis = require('ioredis-mock')
if (typeof Redis === 'object') {
// the first mock is an ioredis shim because ioredis-mock depends on it
// https://github.com/stipsan/ioredis-mock/blob/2ba837f07c0723cde993fb8f791a5fcfdabce719/src/index.js#L100-L109
return {
Command: { _transformer: { argument: {}, reply: {} } }
}
}
// second mock for our code
return function(...args) {
const instance = new Redis(args)
mocks.redis = instance
return instance
}
})
Reopening this as it would be really helpful to have this information in the readme 🤔
I'm currently struggling with this, and I just wanted to add my thoughts.
There is another way to add ioredis-mock
to jest, like this:
jest.setMock('ioredis', require('ioredis-mock'));
const ioredis = require('ioredis');
This will then mock out any references to ioredis
in the codebase.
This is quite a nice way of doing this, however this is problematic because a Redis instance is not created in the test, but in the codebase, and I can't see how to add test data to the mock redis instance.
It would be nice if it was this simple to mock ioredis
, and there was an easy way to add data at this point so that when the Redis instance is instantiated the data is then applied. I have no idea how to achieve this.
This seemed to work:
in __mocks__/ioredis.js
const ioredisMock = require('ioredis-mock')
jest.mock('ioredis', () => {
const Redis = require('ioredis-mock')
if (typeof Redis === 'object') {
// the first mock is an ioredis shim because ioredis-mock depends on it
// https://github.com/stipsan/ioredis-mock/blob/master/src/index.js#L101-L111
return {
Command: { _transformer: { argument: {}, reply: {} } },
}
}
// second mock for our code
return Redis
})
module.exports = ioredisMock
None of the above worked for us, the following did:
// On the jest setup file: __test__/setup/index.ts
const ioredisMock = require('ioredis-mock')
jest.setMock('ioredis', ioredisMock)
@santiagofm solution worked for me.
I had to make sure that I put it in a script which is configured in my jest.config.js
setupFiles
NOT setupFilesAfterEnv
.
@sjclemmy Your method of mocking Redis allows you to write directly to Redis using the mock client within the test itself. For example if you're using Jest you create a test harness to preload data before each test using all the standard ioredis methods and the data would be written in-memory.
I prefer this over passing a data object to the RedisMock constructor. In my service/application I have specialized DAOs/DTOs to read/write data to Redis. Not sure what your application does but if you follow this pattern and create your own set of DAOs and DTO's then you can very easily test by using ioredis-mock to mock all those calls out to Redis.
what is the best way to clean up redis data among tests?
Starting in v5.4
Jest module mocking has first class support:
jest.mock('ioredis', () => require('ioredis-mock/jest'));
@stipsan even with that I still get
TypeError: Cannot read properties of undefined (reading '_transformer')
at Object.<anonymous> (../node_modules/ioredis-mock/lib/command.js:69:34)
at Object.<anonymous> (../node_modules/ioredis-mock/lib/commands/defineCommand.js:16:39)