Jest: Disable all manual `__mocks__` at once with `jest.unmockAll()`.

Created on 16 Aug 2019  路  18Comments  路  Source: facebook/jest

馃殌 Feature Proposal

Disable all manual __mocks__ at once with jest.unmockAll(), including the manual mocks for node_modules.

Motivation

Historically, the main pattern for testing UI was to focus on unit tests that tested UI components in isolation. With this pattern, it is common to mock modules so the internal details of nested components would not leak into the tests of the component under test. One canonical example was to provide manual mocks for common shared component (via __mocks__) so the dom elements of the nested components would not show up in snapshots.

With the advent of new testing libraries such as testing-library, there has been a trend to write tests that closely resemble how components are used. In practice, that usually means mocking as little as possible. In this context, we generally do not want third-party node_modules to be mocked and we have to call unmock for all the manual node modules __mocks__ that the component under test indirectly relies on.

This is very cumbersome for a couple of reasons:

  1. There might be many of those manual __mocks__ in codebases that either want to support both testing patterns at the same time, or that are slowly migrating toward one or the other pattern.
  2. There is no way to know what __mocks__ modules were used by jest during a test. You currently need to check manually the code of every single indirect dependency of a module.

This is completely impractical for large codebases.

The same way we can disable all auto-mocked modules with jest.disableAutomock(), there should be a way to disable all manually mocked modules with jest.unmockAll(). Ideally, we would also expose a manualMock boolean parameter in jest config so it can be disabled across multiple tests at once.

Example

jest.unmockAll()

Pitch

Since manual __mocks__ are a jest concept, the only way to change is to update jest itself.

I'm happy to send a PR if the maintainers of jest are fine with this feature. Let me know and I'll work on it asap.

Cheers

EDIT: Made it clearer that the problem is for __mocks__ of node_modules

Feature Request

Most helpful comment

I see - thanks for trying to help me out. I can workaround this for now by calling unmock() repeatedly.

That said, I think it would be helpful to other people to have a simple way to disable all jest magic in a given test if need be and I'm happy to send a PR if you are onboard with this change.

All 18 comments

Thanks for the suggestion. I didn't quite understand why this is needed for manual mocks. Given that, unlike automocks, you have a jest.mock somewhere that activates them, why not just remove that line?

That's not the behavior I'm seeing with 24.8.0. jest will automatically mock node_modules modules for which there are manual __mocks__. If I remember correctly, it's been like that since the very early days of jest. Would you like me to send a repro?

From the documentation:

If the module you are mocking is a Node module (e.g.: lodash), the mock should be placed in the __mocks__ directory adjacent to node_modules (unless you configured roots to point to a folder other than the project root) and will be automatically mocked. There's no need to explicitly call jest.mock('module_name').

Actually it looks like third-party / npm modules are treated differently than regular modules.

When a manual mock exists for a given module, Jest's module system will use that module when explicitly calling jest.mock('moduleName'). However, when automock is set to true, the manual mock implementation will be used instead of the automatically created mock, even if jest.mock('moduleName') is not called. To opt out of this behavior you will need to explicitly call jest.unmock('moduleName') in tests that should use the actual module implementation.

So my feature proposal is that jest.unmockAll() unmocks all modules (ie both npm modules and user modules).

Additionally, I would argue that when automock is off, jest should not mock any module at all (ie not even npm modules). I find that very confusing that when automock is disabled, jest still mock some modules. But since that would introduce a breaking change, I understand why we may want to punt on that.

@jeysal Updated my last comment ^

Yeah I do agree that it would be more intuitive if node_modules were treated the same, but it's very breaking. @SimenB maybe you have some more historical context on this?
Especially given that its use limited to node_modules, I'm not sure the proposed API would be useful to many people, and also I'm worried it might encourage bad practices. Automocking is no longer the default even at FB, most people consider being explicit about mocks and using them sparingly to be the better way. So if a mock is used in some tests but not in others, it might be better to not include it by default, but explicitly include it in some tests.
For normal files, this is just the standard jest.mock('./something'), for node_modules this can still be achieved via jest.mock('something', () => require('../global-mocks/something')).

I fully agree with you that it's much better to be explicit. That's exactly why I want a way to disable all jest mocking / magic with one command. I don't want any mocking happening in my test file unless I specifically call mock(). The current pattern of being to define global __mocks__ completely breaks this because you cannot easily disable that in a given test file.

I'm not sure I follow why it would encourage bad practices. To the contrary, it would allow people to slowly migrate away from having their tests depending on node modules __mocks__.

Oh so you want to unmockAll then explicitly mock some immediately afterwards?

Yeah I potentially might mock some modules afterwards. Essentially, I just want to tell jest don't mock anything unless I told you to.

Okay. Of course I'd very much prefer to just make that the default, hopefully @SimenB knows more about how it came to be like this (node_modules still effectively having automock: true).
For now, would the last sentence of my comment help you? (It's essentially avoiding the special mocking behavior for node_modules and using regular mocking mechanics for them instead)

Yes I'm aware we can manually mock a module in a test using jest.mock if that's what you are asking. But that does not help me disable manual __mocks__ for node_modules. For that, I have to repeatedly call jest.unmock() for each node_modules that have been globally mocked. I tried to automate this with a util function but that does not work because I think the jest.unmock calls have to be statically analyzable and / or in the top-level scope of the test module.

Of course you'd have to remove the node_modules __mocks__ in favor of the global-mocks, hence

avoiding the special mocking behavior for node_modules and using regular mocking mechanics for them instead

Are you saying that manual mocks (ie __mocks__) should be avoided? I knew that automocking was now somehow deprecated but it's the first time I hear that __mocks__ are bad because they cannot be disabled in tests. If that's case, I think we should make that very clear in the guide for manual mocks and let people know about it. Without this, people are going to keep running into that issue where they want to disable one global mock for a node_module but have to refactor all their test to accomplish this. It seems to me that exposing a jest.unmockAll() is more pragmatic and more consistent with the approach we took in the past with automocking.

No, it's great in general. It's just that the special behavior for node_modules does not work well for you (and I can see why) and I am trying to help you find a quick solution for that particular aspect.

I see - thanks for trying to help me out. I can workaround this for now by calling unmock() repeatedly.

That said, I think it would be helpful to other people to have a simple way to disable all jest magic in a given test if need be and I'm happy to send a PR if you are onboard with this change.

Yes, why is this not a thing?? Pretty wild that its not!

This is a huge necessity for me

I would also love to be able to configure our jest setup to not auto-import node modules mocks so we can choose to mock at the file level. In a large codebase, the auto-mocking has a large blast radius.

I also agree that we need a way to disable manual mocks in case we have different types of tests. e.g integration or e2e. In the mean time I found a work around for this that avoids having to call jest.unmock everywhere.

I make jest ignore path patterns for __mocks__ made for special cases in my code when I don't use automock, as well as ignore the root __mocks__ meant to manual mock node_modules on special cases where automocks couldn't do the job.

modulePathIgnorePatterns: ['<rootDir>/src/.*/__mocks__', '<rootDir>/__mocks__'],

If by any reason, you still need to mock certain libs in your integration tests that are used in every test file then you can pass jest a node script in setupFilesAfterEnv that calls your jest.mock on whatever module you need and provide it the manual mock there. Or as stated above, call jest.mock on each test file separately. It's a bit of a wrapper while we get this feature figured out.
e.g jest.mock('<module name>', () => require('./__mocks__/<module name>'));

Was this page helpful?
0 / 5 - 0 ratings