salesforce / sfdx-lwc-jest

Run Jest against LWC components in SFDX workspace environment
MIT License
166 stars 82 forks source link

Auto resolve imports to lwc/componentFoo/__mocks__/componentFoo.js #379

Closed nwcm closed 4 months ago

nwcm commented 4 months ago

Update LWC resolver to attempt to resolve to **/lwc/componentName/__mocks__/componentName.js first, then fallback to the actual implementation.

This avoids having to set many moduleNameMapper items or defining mocks in a separate directory from the components which complicates upkeep.

If there was a way to honor jest.requireActual() that would be great, but I'm not familiar with how to implement that

nwcm commented 4 months ago

@nolanlawson There's is one thing before we progress.

Currently it will resolve the mock first and then fallback to the implementation. Which is great for all jests generally to avoid tests spanning over components. However, as it is currently when you want to test the actual component. c/componentName resolves to the mock, so you would need to change it to ../componentName for the import in the jest file.

This would break everyone's tests if they upgraded!

It doesn't look like we have enough context in the resolve to make a distinction on import from test file vs component file. I'll need to look at the transformer

nwcm commented 4 months ago

As for https://github.com/salesforce/lwc-test/pull/260, yes looking to get both merged

nolanlawson commented 4 months ago

However, as it is currently when you want to test the actual component. c/componentName resolves to the mock, so you would need to change it to ../componentName for the import in the jest file.

This isn't even possible for parent-child implicit imports – e.g. <c-foo> in an LWC HTML file will always resolve to c/foo, not ../c/foo. You could maybe fix that with moduleNameMapper, but it would get hairy.

Rather than doing this at the transformer level, I wonder if it would be cleaner to expose a config or something that can be set in the test:

import { setComponentsToMock } from 'somewhere'

beforeEach(() => {
  setComponentsToMock(['c/foo', 'c/bar'])
})

afterEach(() => {
  setComponentsToMock([])
})
nwcm commented 4 months ago

I would expect that any component referenced would use the mock if it is there. Such that tests are shallow and only test the referenced component. So <c-foo> or import Foo from "c/foo"; in the actual component would resolve to the mock, if it is mocked.

The import c/ vs ../ is only for the import in the test file for createElement()

It seems clean to me without having to have additional config or per test file setups. If there was a way to follow jests requireActual() then you could override the test to use the true implementation. If you have any ideas for this, happy to implement

nwcm commented 4 months ago

Some more exploring with Jest and LWC. This may be unnecessary. It's convenient to automatically use any manual mocks created, however, with jest you can use jest.mock('c/foo'); in the test file to apply the manual mock from __mocks__. However, you would need to call this for each component. Whereas I was seeking a way to automatically use manual mocks.

automock=true seems to have a different result and not use the manual mocks as I think it should be. That may be something with how LWC and Jest interact

https://jestjs.io/docs/manual-mocks

nwcm commented 4 months ago

Options I see.

Manual mocks in */lwc/foo/__mocks__/ and manual pointers

Update resolver to resolve to manual mocks first and define mocks in */lwc/foo/__mocks__/.

There are quite a few issues with this change, so will abandon it.

I think a setupLwcMocks.js setup file which is dynamic from a yarn script

from pathlib import Path

JEST_MOCK_SETUP_FILE = "setupLwcMocks.js"

with open(JEST_MOCK_SETUP_FILE, 'w',encoding="utf-8") as file:
    for path in Path('force-app/main').rglob('lwc/*/__mocks__/*.js'):
        print(path.stem)

        file.write(f'jest.mock(\'c/{path.stem}\');\n')
jest.mock('c/foo');
jest.mock('c/bar');
jest.mock('c/cookie');
jest.mock('c/woo');
nolanlawson commented 3 months ago

Thanks for providing so many details! This is helpful for anyone else trying to solve this problem. 💪