Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial experimental Jest support #25055

Merged
merged 4 commits into from Apr 26, 2023
Merged

Initial experimental Jest support #25055

merged 4 commits into from Apr 26, 2023

Conversation

dgp1130
Copy link
Collaborator

@dgp1130 dgp1130 commented Apr 24, 2023

PR Checklist

Please check to confirm your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

  • Feature

What is the new behavior?

This PR adds initial experimental Jest support. This is experimental and not ready for production use yet. For now, it it sufficient to pass the tests generated by the ng new application and we will continue to expand on this design going forward.

Enable the Jest runner by updating the test builder in your angular.json to be:

{
  "projects": {
    "my-app": {
      "architect": {
        // ...
        "test": {
          "builder": "@angular-devkit/build-angular:jest"
        }
      }
    }
  }
}

The current approach is to pre-build application tests with the browser-esbuild Angular builder under the hood, and then run Jest on the JavaScript outputs. We'll continue to experiment with this architecture and others to see what the best path forward is in terms of performance, maintenance, and developer ergonomics.

Does this PR introduce a breaking change?

  • No

@dgp1130 dgp1130 added action: review The PR is still awaiting reviews from at least one requested reviewer target: rc This PR is targeted for the next release-candidate labels Apr 24, 2023
@dgp1130 dgp1130 force-pushed the jest branch 5 times, most recently from 19f5ad6 to b492080 Compare April 25, 2023 22:08
Copy link
Collaborator

@alan-agius4 alan-agius4 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dgp1130, please also squash the commits as in the CLI we do not use fixup.

For now this just runs ESBuild-er to build test code, Jest is not actually invoked yet.

This uses `glob` to find test files matching the given pattern. I went out of my way to limit `glob` functionality as much as possible in case we change the implementation later.
This runs Jest on the outputs of the built test files and reports the results of the test execution. It depends on `jest` and `jest-environment-jsdom` as optional peer deps validated in the builder, so there isn't a strict dependency on Jest for applications which don't use it.

Jest exports a `runJest()` function, however it can't be used directly because we need to opt-in to `--experimental-vm-modules` in ordre to run Jest on native ESM JavaScript as produced by `browser-esbuild`. This means we need a Node subprocess in order to add this flag, because the `ng` command cannot add a Node flag to its own current execution. This unfortunately means we can't just `import * as jest from 'jest';` or even `require.resolve('jest')` because that returns the library entry point exporting `runJest()`, rather than a script which actually runs Jest on load. Fortunately, Jest exports it's `node_modules/.bin/jest` entry point from its `package.json` under `jest/bin/jest`, so we `require.resolve()` _that_ to get the path to the correct file.

Executing Jest is fairly straightforward, running on the output of the `browser-esbuild` execution with outputs streamed to the console. We opted to use JSDom over Domino in order to align with the existing Jest ecosystem.
This configures polyfills to set up the environment before executing Jest tests. We need to do three things:
1. Set the global `jest` symbol. Jest executing in ESM does not provide the `jest` global and users are expected to import from `@jest/globals` or `import.meta.jest`. Zone.js is not compatible with this yet, so we need to manually define the `jest` global for Zone to read it.
2. Run user polyfills, (typically including `zone.js` and `zone.js/testing`). Zone reads the `jest` global to recognize the environment it is in and patch the relevant functions to load fake async properly. Users can override this part if they are building a Zoneless application or have custom polyfills for other browser functionality.
3. Initalize `TestBed`. This configures the `TestBed` environment so users don't have to manually configure it for each test file.

Ordering is very important for these operations, which complicates the implementation somewhat. `zone.js/testing` does not include an import on `zone.js`, meaning there was no guarnatee the bundler would sort their executions in the correct order. Similarly, `zone.js` does not import anything from Jest, so it is not trivial to inject the `globalThis.jest = import.meta.jest;` line before Zone loads. Even setting polyfills to `[jestGlobal, 'zone.js, 'zone.js/testing', initTestBed]` doesn't work because code splitting rearranges the order of operations in an incompatible way. Instead, these are implemented as distinct entry points in `browser-esbuild` with Jest's `--setupFilesAfterEnv` option executing them in the correct order.

Ideally, we could drop the global initialization altogether once Zone.js knows to look for `import.meta.jest` in an ESM context. Also we might be able to reduce down to a single polyfills entry point if `zone.js/testing` had an import on `zone.js` to apply correct ordering.
This test generates an `ng new` project, updates it to use Jest for test executions, and then runs `ng test` and checks for the experimental notice.
@dgp1130 dgp1130 added action: merge The PR is ready for merge by the caretaker and removed action: review The PR is still awaiting reviews from at least one requested reviewer labels Apr 26, 2023
@angular-robot angular-robot bot merged commit bcba41f into angular:main Apr 26, 2023
21 checks passed
@dgp1130 dgp1130 deleted the jest branch April 26, 2023 22:41
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators May 27, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
action: merge The PR is ready for merge by the caretaker target: rc This PR is targeted for the next release-candidate
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants