angular-cli icon indicating copy to clipboard operation
angular-cli copied to clipboard

Exposing the built-in jest config

Open danny12321 opened this issue 1 year ago • 20 comments

Command

config

Description

Expose the jest config used by angular so third party apps can use it. For example: StrykerJS needs this config to run mutation testing on angular project.

Describe the solution you'd like

It would be nice to have something simular as npx ng generate config karma but than for jest.

Describe alternatives you've considered

I tried reverse engineer the config but it was quite complex and may be prone to change in the future which increases the potential of errors with new releases.

danny12321 avatar Jun 23 '23 14:06 danny12321

it was quite complex and may be prone to change in the future which increases the potential of errors with new releases.

This is exactly why the Jest config is not exposed right now. We've talked about this a good amount on the team to understand the trade-offs being made here. The challenge is that Angular CLI has to make a lot of assumptions about Jest's setup and configuration to provide a reliable experience and for us as developers to effectively reason about what your Jest execution actually looks like and how we can improve it. This is easy to do when we control that configuration and can specify it exactly. When we start allowing user-specific configurations, it becomes very easy for users to break that configuration and run into confusing edge cases.

One of the primary goals of the Angular CLI is to solve these kinds of tooling and configuration problems so users don't have to deal with them as much as possible. We typically try to enable common web development use cases directly and provide a holistic experience as much as we can, rather than implementing a minimal core and asking users to fill in the blanks. To that end, one area we're experimenting with here is trying to see how far we can go without needing to expose the Jest configuration. Is it possible for Angular CLI to support most common testing use cases through other means without having to expose the Jest config?

As one example, I expect some users might think "I want to modify the Jest configuration so I can measure code coverage". That's certainly one approach, but code coverage is a pretty common task I would much rather just support code coverage in Angular CLI directly and say "Just use that". Let Angular CLI handle the Jest configuration for you and solve the problem more directly. We don't yet have support for code coverage in the current Jest implementation, but it is something we're hoping to add following this mindset.

Jest support is still experimental, and I think the answer to the above question remains to be seen. What I think would be helpful here is understanding everyone's motivations for modifying the Jest configuration. Mutation testing is one such use case. I'm not terribly familiar with it and will need to do more research to make sure this kind of approach would be the right way of integrating it. But I would love to hear from the rest of the community about what problems you all are trying to solve. If we can collect enough use cases here, I think it will greatly help to answer the above question and understand whether or not exposing the Jest config is truly the best path forward for the Angular ecosystem.

dgp1130 avatar Jun 27 '23 22:06 dgp1130

@dgp1130, thanks for your response.

Believe me when I say: we understand. I think using something like npx ng generate config jest is a suboptimal approach. I would rather have something like import jestConfig from '@angular-devkit/build-angular/src/builders/jest/config.mjs'. That way, we have the best of both worlds: a great user experience and a way for plugin creators to get at the jest config.

An alternative:

import { createJestConfig } from '@angular-devkit/build-angular';

const projectName = 'app';
const jestConfig = await createJestConfig(projectName);

nicojs avatar Jul 05 '23 21:07 nicojs

@dgp1130 would you agree with this approach? I would be happy to implement this in a PR 👍

nicojs avatar Sep 22 '23 17:09 nicojs

@dgp1130 - I agree with the intent of automating/hiding the jest config. I've certainly found it to be brittle when working with Angular, and particularly when upgrading to ESM. I think there's a lot of value in wrapping/hiding it in most cases. The question is whether completely hiding it without providing an escape hatch is sufficient in 100% of cases.

Let me try to add a little value by inventorying what we've had to configure in jest.config.js and in karma.conf.js. (I'm including karma just b/c Angular does support the config file, and it's worth considering why it's needed):

karma.conf.js settings:

  • plugins (reporters, coverage plugins, browser launchers)
  • Reporter settings
  • Launcher settings
  • log level
  • single run
  • restart on file change

jest.config.js settings:

  • Things presumably handled by the builder:
    • roots
    • testMatch glob pattern
    • transform
  • testPathIgnorePatterns
  • moduleDirectories
  • moduleFileExtensions
  • moduleNameMapper - Fixup all the packages that don't have correct node ESM packaging
  • setupFilesAfterEnv

Things that we've needed to pass to jest on the command-line:

  • environment variables
  • switches like coverage and reporter parameters
  • Run test on single file or path pattern
  • Debug watch

Hope that helps.

johncrim avatar Sep 29 '23 18:09 johncrim

@nicojs, that's definitely an approach to consider, but I think it still has the same challenges of exposing the underlying implementation details (albeit in a more constrained and less accessible capacity). For example, one aspect we're experimenting with right now is pre-building tests with esbuild prior to passing them through to Jest. This means Jest is operating on pure JavaScript files and performs no transformations whatsoever. If a user then tries to add a custom transform expecting TypeScript files, it's not going to find anything. Future changes to aspects of the build process become observable to custom plugins and break them in a hard to anticipate way. Basically any change is a potential breaking change and it becomes increasingly difficult to do anything with the system outside of major versions.

There is potentially some API design here which could minimize that risk such as allowing customization of specific pieces of the Jest config as managed by Angular. A strawman example for this might look like:

import { runJest } from '@angular-devkit/build-angular';

runJest(['**/*.spec.ts'], {
  // Override specific, supported pieces of the Jest config.
  moduleNameMapper: { /* ... */ },
});

That's definitely not perfect either, but it's an approach to consider which might serve as a useful middle ground for supplying some constraints to the system without being completely closed off to extensions.

At this stage though I think the most helpful thing would just be to understand use cases for modifying the Jest config and if/how it would make sense for Angular CLI to meet those needs (whether that includes exposing the Jest config or addressing the problem through a different means).

dgp1130 avatar Oct 11 '23 00:10 dgp1130

Hi @dgp1130 🙋‍♂️, thanks for the help 🙏

Wouldn't it be possible to run esbuild using the jest transformer API?

If running esbuild from a jest transformer API is not possible, then a drop-in replacement for jest would be nice to work with. However, I would like to point you to a new programmatic API I've been working on. Preferably, it would use that API: https://github.com/jestjs/jest/pull/14062

So something like this:

import {createJest} from '@angular-devkit/build-angular';

const jest = await createJest();
jest.globalConfig = {
  ...jest.globalConfig,
  // Override specific, supported pieces of the Jest config.
  collectCoverage: false,
};
const {results} = await jest.run();
console.log(`run success, ${result.numPassedTests} passed tests.`);

nicojs avatar Oct 14 '23 19:10 nicojs

Wouldn't it be possible to run esbuild using the jest transformer API?

Possibly, though Angular's build process is a lot more complicated than just running esbuild. Angular CLI includes a complete build system for orchestrating work like this, and I think we would rather decouple the building and testing parts of the infrastructure as much as possible so Jest doesn't need to have knowledge of Angular's build processes. That does lead to some performance questions as I mentioned and we're still trying to figure out if that is quite the right approach, but it's something we're at least hoping to experiment with.

As for the programmatic API, technically we can't easily use that right now anyways just because we are using native ESM and need to spawn a Node process with --experimental-vm-modules. Angular CLI won't have that flag (unless users remember to pass it when running ng test) so we need to spawn a second Node process just to set the flag. As a result, running Jest as a separate Node process with --expeirmental-vm-modules is the best place to do that.

We could avoid that and fork the Angular CLI process instead, but the Jest programmatic API would need to be useful enough to make that desirable. Overriding the config is something we could already do even when running Jest as a separate process, so I'm not sure how useful it would be in this particular case. Structured test result data would be nice, but we'd probably rather rely on Jest's built-in reporter instead of trying to build our own on top of a generic test result data structure. Are there specific features planned for this which might be useful for Angular?

dgp1130 avatar Oct 17 '23 21:10 dgp1130

As for the programmatic API, technically we can't easily use that right now anyways just because we are using native ESM and need to spawn a Node process with --experimental-vm-modules.

This would be fine for StrykerJS, since Stryker spawns a separate process for a test runner anyway. So we can already pass --experimental-vm-modules that way.

As a result, running Jest as a separate Node process with --experimental-vm-modules is the best place to do that.

Yes, that is what we're planning to do.

We could avoid that and fork the Angular CLI process instead, but the Jest programmatic API would need to be useful enough to make that desirable.

If I understand this correctly, you're opting to do Angular work with just the normal jest CLI, but with a build step before running the tests, is that correct? This would put Angular back in the camp that says: "let's not recreate our build pipeline in our test pipeline". I would love that 😻! That would mean that Stryker would work with a simple --buildCommand (something like ng build --test). We could run jest as we would normally.

nicojs avatar Oct 18 '23 13:10 nicojs

If I understand this correctly, you're opting to do Angular work with just the normal jest CLI, but with a build step before running the tests, is that correct? This would put Angular back in the camp that says: "let's not recreate our build pipeline in our test pipeline". I would love that 😻!

That's the idea. It's still TBD if this will actually work out in practice given performance and maintainability concerns, but it's what we're trying.

That would mean that Stryker would work with a simple --buildCommand (something like ng build --test). We could run jest as we would normally.

I would love to support something like this, though unfortunately it's not quite that simple. I think it's worth considering adding this kind of functionality, but that's a separate question from the Jest configuration. I created https://github.com/angular/angular-cli/issues/26132 to track that specific feature request.

dgp1130 avatar Oct 25 '23 23:10 dgp1130

I'm currently working with angular-builders/jest and jest-preset-angular and would like to convert to built in runner if possible. Here are the things we are configuring in jest.config.js:

  • transform: pug -> we will be removing this as we convert to new angular control syntax with html. Not needed, but I can see where other transforms may be desired
  • testEnvironmentOptions.url -> we have some tests that create full URLs. This setting was necessary to eliminate surprise base domains.
  • cacheDirectory -> We run our entire local dev environment inside docker and we need this for performance
  • moduleNameMapper -> We have custom tsconfig paths and this is required to make Jest obey them. Ideally, this would be built in to your solution and not require custom config

swebber-upngo avatar Nov 15 '23 16:11 swebber-upngo

I recently upgraded a project to A16 and wanted to move to Jest with the built in runner. I also found myself having to use angular-builders/jest and jest-preset-angular

The blocker I ran into was that the built in runner doesn't handle SCSS @use -- some relationship to #25131 ?

(I'm also using moduleNameMapper, which given the previous comment, also seems like it would be an issue?)

a-boertien avatar Nov 17 '23 00:11 a-boertien

I would love to use the ng jest runner as well, but as of Sept (and I don't believe there have been any updates since) there are a lot of gaps between what the current ng jest runner can do and what is needed to use it in production - see my list above.

Right now jest-preset-angular is incompatible with typescript 5, and typescript 5 is required by ng 17.

johncrim avatar Nov 21 '23 01:11 johncrim

For anyone interested in jest support for angular. I've found and documented a way to debug jest tests, see #27117.

I've also found a way to run StrykerJS with an Angular project using the experimental jest support: see https://stryker-mutator.io/docs/stryker-js/guides/angular/#angular-with-experimental-jest-support.

I would still love to resolve this issue, though.

nicojs avatar Feb 16 '24 12:02 nicojs

Adding our use case to this, we need a way to generate jest-junit reports for integration with the build pipeline. The package has ~5M weekly downloads, so I assume that would be a common requirement.

vypsim avatar Feb 16 '24 23:02 vypsim

Hi there and first of all thanks for adding native jest support!

This might have been implicitly mentioned above already in this list as setupFilesAfterEnv but I'd like to add this still:

We're using Testing Library Angular and its custom DOM matchers jest-dom. Usually I'd add custom matchers in a setup-jest.js file.

PhOder avatar Feb 17 '24 08:02 PhOder

Hello. Thanks for adding Jest support. It's a really great to see you guys supporting other options besides Karma and Jasmine.

I'd like to also note that this is currently not working if you use any sort of @layer in your CSS. That's because Jest uses jsdom which uses cssom and cssom doesn't support @layers or @support at the moment.

We need access to disable CSS parsing errors but we can't because we can't use our own jest.config.js. We'd like to use PrimeNG but we can't due to this such as https://github.com/jsdom/jsdom/issues/2177#issuecomment-426825829

https://github.com/primefaces/primeng/issues/14085 - PrimeNG issue which references... https://github.com/jsdom/jsdom/issues/2026 - JSDom which references... https://github.com/NV/CSSOM/issues/109#issuecomment-1398336817 - CSSOM

~I'd say this is pretty big issue because we use @supports all the time and I'd argue a lot of enterprise companies use it.~ UPDATE 1 Looks like they do support @supports now. But layers don't work.

UPDATE 2 - It looks like it doesn't support most @ properties like @container either. So this is a bigger issue than I thought as CSSOM is no longer maintained and there's no mention of JSDOM using a different CSS parser.

tsteuwer-accesso avatar Mar 18 '24 20:03 tsteuwer-accesso

Hello,

I stumbled accross this issue as I did an upgrade of an existing project to Angular 18 and noticed that Karma has a deprecation notice, see https://github.com/karma-runner/karma.

When I tried moving to jest many of the existing tests failed and many jasmine matchers (e.g. toBeTrue, toHaveBeenCalledOnceWith ...) did not work anymore, for this I usually go with jest-extended, see https://github.com/jest-community/jest-extended

Following the guide https://jest-extended.jestcommunity.dev/docs/getting-started/setup I added this to my package.json

"jest": {
  "setupFilesAfterEnv": ["jest-extended/all"]
}

Then I got a warning which linked me to this issue.

Personally I find it awesome that jest gets native support by angular but the limitations make it really difficult to migrate for us.

Also we seem to have issues with tests that utilize Angular Materials in some way.

I hope this issue gets more visibility and might get some resolution as it would be a dream to get jest working with "ng test" without having to rewrite all tests, just because we can't extend the configuration ...

dsteindo avatar Jun 11 '24 15:06 dsteindo

I hope this issue gets more visibility and might get some resolution as it would be a dream to get jest working with "ng test" without having to rewrite all tests

Web Test Runner is intended to be the migration target from Karma as that will allow us to drop Jasmine in basically unchanged and should be much easier to migrate to. Jest is intended as an option for new apps to consider or existing apps who are willing to spend to a non-trivial amount of effort updating their tests and think that's worth the investment.

dgp1130 avatar Jun 11 '24 15:06 dgp1130

It might also be that I have issues grasping what actually needs to be done to migrate an existing project to Web Test Runner.

For now I followed these guide https://blog.angular.dev/moving-angular-cli-to-jest-and-web-test-runner-ef85ef69ceca

So maybe for me to understand is that we should no longer use ng test and switch to npm test as descibed here https://medium.com/@ayushgrwl365/jesting-your-angular-app-simplifying-unit-testing-with-jest-324f5bb9e2df ^ this article also explains a way how I can use setupFilesAfterEnv in some capacity

there is also this article: which I find even harder to understand: https://medium.com/@rathoreaparna678/moving-angular-cli-to-jest-and-web-test-runner-d2966e1c5802

I definetly have to try out more things as the goal would be to be able to use jest-extended in an angular project,

It also might just be that much of the information on the internet might conflict and there are many articles but rarely a github repo or something is linked to see it in action.

Update: I got it working what I wanted and created a github repository ...

---> https://github.com/dsteindo/jest-extended-angular

dsteindo avatar Jun 11 '24 15:06 dsteindo

Both Web Test Runner and Jest support are still experimental and in-progress. We're not at the stage of authoring comprehensive docs yet, so it does take some effort to figure out. I wouldn't recommend moving a production Angular application to them just yet, and the Karma team has committed to maintaining support until Angular is able to support a move off of it, so there's no urgent need for you to do anything about this today. But do feel free to experiment with WTR or Jest and report any issues or limitations you notice.

So maybe for me to understand is that we should no longer use ng test and switch to npm test as descibed here https://medium.com/@ayushgrwl365/jesting-your-angular-app-simplifying-unit-testing-with-jest-324f5bb9e2df ^ this article also explains a way how I can use setupFilesAfterEnv in some capacity

That's using jest-preset-angular, which is a different project. This issue is talking about the @angular-devkit/build-angular:jest builder directly supported in the CLI. You should be able to use ng test just fine with it.

dgp1130 avatar Jun 11 '24 20:06 dgp1130