jest-preset-angular: Unable to use services exported under a namespace w/ Jest 27 + Ng12

🐛 Bug Report

Attempting to test components that inject a service imported from a namespace fails in Jest 27 / Angular 12.

To Reproduce

  1. ng new my-app
  2. Install jest / jest-preset-angular
  3. Create a new folder, services.
  4. Create a file in that folder (my-service.ts) containing the following:
import { Injectable } from '@angular/core';

@Injectable({
    providedIn: 'root'
})
export class MyService {
    constructor() {
        console.log('HELLO')
    }
}
  1. Create a new file (index.ts) inside the services folder containing the following:
import * as Services from './my-service';

export { Services }
  1. Add private myService: Services.MyService to the constructor of app-component
  2. Import Services in app-component.
  3. Try to run tests with npx jest

Expected behavior

Tests run successfully

Link to repo (highly encouraged)

https://github.com/AgentEnder/ng-jest-issue-6097

Error log:

 Can't resolve all parameters for AppComponent: (?).

      at syntaxError (../packages/compiler/src/util.ts:108:17)
      at CompileMetadataResolver.Object.<anonymous>.CompileMetadataResolver._getDependenciesMetadata (../packages/compiler/src/metadata_resolver.ts:1010:27)
      at CompileMetadataResolver.Object.<anonymous>.CompileMetadataResolver._getTypeMetadata (../packages/compiler/src/metadata_resolver.ts:889:20)
      at CompileMetadataResolver.Object.<anonymous>.CompileMetadataResolver.getNonNormalizedDirectiveMetadata (../packages/compiler/src/metadata_resolver.ts:387:18)
      at CompileMetadataResolver.Object.<anonymous>.CompileMetadataResolver.loadDirectiveMetadata (../packages/compiler/src/metadata_resolver.ts:238:41)
      at ../packages/compiler/src/jit/compiler.ts:137:36
          at Array.forEach (<anonymous>)
      at ../packages/compiler/src/jit/compiler.ts:135:65
          at Array.forEach (<anonymous>)
      at JitCompiler.Object.<anonymous>.JitCompiler._loadModules (../packages/compiler/src/jit/compiler.ts:132:71)
      at JitCompiler.Object.<anonymous>.JitCompiler._compileModuleAndAllComponents (../packages/compiler/src/jit/compiler.ts:117:32)
      at JitCompiler.Object.<anonymous>.JitCompiler.compileModuleAndAllComponentsAsync (../packages/compiler/src/jit/compiler.ts:69:33)
      at CompilerImpl.Object.<anonymous>.CompilerImpl.compileModuleAndAllComponentsAsync (../packages/platform-browser-dynamic/src/compiler_factory.ts:69:27)
      at TestingCompilerImpl.Object.<anonymous>.TestingCompilerImpl.compileModuleAndAllComponentsAsync (../packages/platform-browser-dynamic/testing/src/compiler_factory.ts:59:27)
      at TestBedViewEngine.Object.<anonymous>.TestBedViewEngine.compileComponents (../packages/core/testing/src/test_bed.ts:366:27)
      at Function.Object.<anonymous>.TestBedViewEngine.compileComponents (../packages/core/testing/src/test_bed.ts:155:25)
      at src/app/app.component.spec.ts:10:8
      at ZoneDelegate.Object.<anonymous>.ZoneDelegate.invoke (node_modules/zone.js/bundles/zone-testing-bundle.umd.js:407:30)
      at ProxyZoneSpec.Object.<anonymous>.ProxyZoneSpec.onInvoke (node_modules/zone.js/bundles/zone-testing-bundle.umd.js:3765:43)
      at ZoneDelegate.Object.<anonymous>.ZoneDelegate.invoke (node_modules/zone.js/bundles/zone-testing-bundle.umd.js:406:56)
      at Zone.Object.<anonymous>.Zone.run (node_modules/zone.js/bundles/zone-testing-bundle.umd.js:167:47)
      at Object.wrappedFunc (node_modules/zone.js/bundles/zone-testing-bundle.umd.js:4250:34)

envinfo

System:
    OS: Ubuntu (tested under wsl2)

Npm packages:
    jest: 27.0.5
    jest-preset-angular: 9.0.4
    typescript: 4.2.3

About this issue

  • Original URL
  • State: open
  • Created 3 years ago
  • Reactions: 8
  • Comments: 39

Most upvoted comments

@Maximaximum I just found a new workaround that you can adjust your tsconfig.spec.json to have

{
   //...
  "include": ["src/**/*.ts"]
}

at least it fixed the issue with the sample repo.

The problem I think is similar to #1199 is that: Angular doesn’t support transpile ts to js in “isolated way”. Angular always requires one single TypeScript Program (see https://github.com/angular/angular/issues/43165) to process all the files together while here with Jest, we split them up into multiple workers. Compilation per worker is not the same as using one single Program.

I am using generated code from apollo-angular like @Maximaximum and was able to fix the issue with an ngcc run and the replacement of

{
   //...
  "include": ["**/*.spec.ts", "**/*.d.ts"]
}

with

{
   //...
  "include": ["**/*.ts"]
}

in tsconfig.spec.json

Currently using jest-preset-angular@9.0.7

@Maximaximum I just found a new workaround that you can adjust your tsconfig.spec.json to have

{
   //...
  "include": ["src/**/*.ts"]
}

at least it fixed the issue with the sample repo.

The problem I think is similar to #1199 is that: Angular doesn’t support transpile ts to js in “isolated way”. Angular always requires one single TypeScript Program (see angular/angular#43165) to process all the files together while here with Jest, we split them up into multiple workers. Compilation per worker is not the same as using one single Program.

This doesn’t seam to work with nx repos and graphql codegen 😞

@ahnpnl Any news regarding properly fixing this issue within jest-preset-angular?

@ahnpnl I’m currently trying to implement a custom Jest resolver, but it seems like a customer resolver won’t be able to fix the issue.

Let’s take a import * as Apollo from 'apollo-angular'; line as an example.

As far as I can see, a Jest resolver only deals with resolving import paths like apollo-angular to actual absolute file paths in the filesystem (like /workspaces/my-project/frontend/node_modules/apollo-angular/bundles/ngApollo.umd.js). But it has nothing to do with handling the * as Apollo part. The resolver doesn’t even get the * as Apollo (or anything like { gql } or someDefaultExport) part as an argument, it has no idea about what values are actually being imported from an es6 module.

I’m facing exactly the same issue! (And it took me a few days to trace it down!).

The issue only happens with Jest (not with Jasmine/Karma) and only if using namespace imports, ie import * as Exported from './exported';.

In my case the issue is that I’m using some code generated by a 3rd party code generator that contains namespace imports, and there’s no way to tweak the code generator’s behavior

I guess all the logic to desugar namespace syntax lies here https://github.com/Maximaximum/jest-namespace-imports-transformer/blob/main/src/transform-script.ts#L88 ? We are happy to add it as a custom AST transformer to internal codes

@ahnpnl I don’t know about the exact use cases of the other folks here, but in my specific case I have dozens of auto-generated files containing namespaced imports. Adding an entry to jest.config.js for each of these imports is definitely not an option, because I’m having an nx workspace with about 20 different angular projects, and each of them having its own jest.config.js. Managing all these moduleNameMapper settings in all of the jest.config.js would be a nightmare!

Creating a custom resolver might be an option, but I’m totally new to Jest, so it might be quite an overwhelming task for me.

As per using path-mapping transformer, it looks like it should be relatively easy to do though. Will give it a try, thank you.

IIRC ng build does similar thing like ngc. ngc is a replacement of tsc which does some Angular things extra.

After producing build outputs, you would need to configure Jest to run on output folder yes.