0

I have seen similar questions but nothing I have seen in the documentation or stackoverflow describes what I am trying to do. I am new to javascript and just started using jest, I have read through the jest documentation but I have not seen an example that mocks a named export of an external library. The library I am trying to mock is rate-limiter-flexible. I want to mock the named export RateLimiterRedis. I need to mock a couple of RateLimiterRedis functions, including get, consume, and delete.

For example when I mocked a function from redis all I had to do was:

import redis from 'redis';
jest.mock('redis', () => {
    return { createClient: jest.fn()};
});

When I try:

jest.mock('rate-limiter-flexible', () => {
    return jest.fn().mockImplementation(() => {
        return { RateLimiterRedis: { get: mockGet } }
    });
});

I get: TypeError: _rateLimiterFlexible.RateLimiterRedis is not a constructor

When I try:

jest.mock('rate-limiter-flexible', () => {
    return { RateLimiterRedis: () => {}}
});

I get: TypeError: limiter.get is not a function
So it recognizes the constructor but I need to add the functions.

I have tried:

jest.mock('rate-limiter-flexible', () => {
    return { RateLimiterRedis: () => {
        return jest.fn().mockImplementation(() => {
            return {
                get: mockGet
            }
        })
    },
            }
});

This also gives: TypeError: limiter.get is not a function

This is in my file I am trying to test:

const limiter = new RateLimiterRedis(opts);

I have also tried doMocking the named export itself (since mock hoists itself to the top) to no success

My question boils down to how can I mock a constructor of a class and that classes functions with jest, when that class is a named export of an external library?

Edit:
mockGets definition:

const mockIpAndUrl ={
    consumedPoints:1
};

const mockGet = jest.fn().mockImplementation(() => {
    return mockIpAndUrl;
})

This does not work:

const mockIpAndUrl ={
    consumedPoints:1
};

const mockGet = jest.fn().mockImplementation(() => {
    return mockIpAndUrl;
})

jest.mock('rate-limiter-flexible', () => {
    return{
        RateLimiterRedis: jest.fn().mockImplementation(() => {
            return { get : mockGet};
        })
    }
});

TypeError: limiter.get is not a function

However, this does:

jest.mock('rate-limiter-flexible', () => {
    return{
        RateLimiterRedis: jest.fn().mockImplementation(() => {
            return { get : jest.fn().mockImplementation(() => {
                return mockIpAndUrl;
            })};
        })
    }
});

This is the documentation I was referring to: https://jestjs.io/docs/en/es6-class-mocks#calling-jestmockdocsenjest-objectjestmockmodulename-factory-options-with-the-module-factory-parameter

This lead me to believe I could use mockGet

Confused
  • 3
  • 5

1 Answers1

0

The export of rate-limiter-flexible is an object that is supposed to have RateLimiterRedis function that returns an instance that has get method.

"I have tried" snippet is wrong because it makes RateLimiterRedis function to return another function, and RateLimiterRedis itself isn't a spy so it cannot be asserted.

It should be:

  jest.mock('rate-limiter-flexible', () => {
    return {
      RateLimiterRedis: jest.fn().mockImplementation(() => ({
        get: mockGet
      })
    };
  });

If RateLimiterRedis is instantiated in top-level imports, mockGet cannot be assigned before it's accessed inside a mock. It can be exported as a part of mocked module:

  jest.mock('rate-limiter-flexible', () => {
    const mockRateLimiterRedisGet = jest.fn();
    return {
      mockRateLimiterRedisGet,
      RateLimiterRedis: jest.fn().mockImplementation(() => ({
        get: mockRateLimiterRedisGet
      })
    };
  });

This allows to import mockRateLimiterRedisGet from rate-limiter-flexible in a test and use for dynamically mocked values and assertions:

mockRateLimiterRedisGet.mockReturnValue(...);
...
expect(mockRateLimiterRedisGet).toBeCalled();
Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • Thank you! This is how it should be done. What I have noticed is, I have mockGet defined properly, but mockGet will not resolve to a function for some reason. I have to do get: jest.fn().mockImplementation(() => { //my mock implementation}), even though that is exactly what I set mockGet equal to. I know mock hoists itself to the top, but I thought anything that started with mock would work, I recall reading that in jests documentation – Confused Jun 24 '20 at 18:38
  • The question doesn't contain mockGet definition. It's not tied to mock hoisting but to import hoisting. This will happen is a module where you need RateLimiterRedis to be mocked is imported instantly. If this is the case, you can try to replace `mockReturnValue(...)` with `mockImplementation(() => ({ get: mockGet }))` as you did before, this will make mockGet lazily accessed. This won't work any way if RateLimiterRedis is instantiated on import. – Estus Flask Jun 24 '20 at 18:58
  • This is what I was talking about: https://jestjs.io/docs/en/es6-class-mocks#calling-jestmockdocsenjest-objectjestmockmodulename-factory-options-with-the-module-factory-parameter – Confused Jun 24 '20 at 19:00
  • Yes, it's a known concern. As I mentioned, mockImplementation instead of mockReturnValue may help. After that whether you can use mockGet or not depends on when `new RateLimiterRedis` happens. It's not always necessary to have mockGet but it's necessary if you need to change returned value dynamically, and it makes it easier to make assertions, `expect(mockGet).toBeCalled()` instead of `expect(RateLimiterRedis.mock.results[0].value.get).toBeCalled()`. – Estus Flask Jun 24 '20 at 19:06
  • If you need to use mockGet, move `file-that-uses-rate-limiter-flexible` import inside a test and change it to `require`. – Estus Flask Jun 24 '20 at 19:09
  • I do need to use mockGet, and even if I didn't it is cleaner that way as you said yourself. I appreciate the help, you answered the next question I had before I could even ask it. You said it was a known concern, do you have a link? Is it like an open issue on github? I could just be bad at googling but I could not find anything related when I searched – Confused Jun 24 '20 at 19:20
  • E.g. https://stackoverflow.com/questions/47397777 . The problem is discussed in the link you posted above, specifically "It's up to you to guarantee that they will be initialized on time". In your case it's likely not because it's accessed the first time before `const mockGet =` is evaluated. So you need to postpone the time when `{ get: mockGet }` object is accessed. If it's accessed on your module's import because `new RateLimiterRedis` happens there, the import needs to be postponed, the only way to do this is to move it inside a test. – Estus Flask Jun 24 '20 at 20:42
  • I updated the post with a recipe how it can be also addressed. – Estus Flask Jun 24 '20 at 20:55