angular / angular

Deliver web apps with confidence 🚀
https://angular.dev
MIT License
95.11k stars 24.9k forks source link

TestBed.configureTestingModule Performance Issue #12409

Closed ollwenjones closed 4 years ago

ollwenjones commented 7 years ago

I'm submitting a ... (check one with "x")

[ ] bug report 
[x] feature request
[ ] support request 

Current behavior

Importing modules into TestBed.configureTestingModule can slow tests down substantially. We have a 'shared module' with parts, that lots of our components use, including a third party library or two. It is very convenient to import this module as a rather than cherry pick in the tests, however testing a small component yesterday, I saw test bootstrap taking two whole seconds and had to cherry-pick dependencies to make tests run reliably.

Expected behavior

TestBed.configureTestingModule should be performant.

Minimal reproduction of the problem with instructions

In this plunker

  1. go to the src/simple.spec.ts and comment and un-comment the SharedModule import into configureTestingModule.
  2. Observe time it takes for test to run.

I am seeing a jump from 0.079s to 0.241s. (~= 3x slower). Multiply that by 5 cases and this simple test takes a whole second. Make the shared module larger and you have real problems.

What is the motivation / use case for changing the behavior?

  1. Slow tests hurt TDD workflow
  2. Slow enough tests disconnect karma from the browser and fail the suite.

Please tell us about your environment:

Win10, VsCode, Webpack,

Note: Even as I write this, I wonder if it's a reasonable expectation for large modules to go through TestBed quickly? Maybe I'm just doing something wrong architecturally, (use multiple, smaller shared modules, not one big one, etc.) However, tests are time-consuming enough without having to track down every individual dependency of a more complex component just to get started.

vvasabi commented 6 years ago

@dasAnderl To get a service injected you could also do:

_restService = <RestService>TestBed.get(RestService);

If you want to make it generic (so no casting is required), you can make a util method that like this:

const get = <T>(type: Type<T>): T => <T>TestBed.get(type);

Then, the above example becomes:

_restService = get(RestService);
brian428 commented 6 years ago

As I mentioned over in https://github.com/angular/angular/issues/13963, the real problem with this workaround is that the dependencies aren't recreated between tests. Meaning any state held by the dependencies can cause bizarre issues. All I think we really need is the option to have the compiled test module reused, since the vast majority of the time is usually taken up in simply compiling templates for the test module.

flemgs commented 6 years ago

@vvasabi Thanks for the workaround, more than 200 tests that took 50 sec before take now 1.5 sec.

JanEggers commented 6 years ago

had the same problem and found this: https://github.com/Quramy/ngx-zombie-compiler

works like a charm

brian428 commented 6 years ago

Unfortunately it doesn't work with Angular 5 because the underlying API changed.

However, TestBed now seems to cache the set of aotSummaries used by the test. So it seems like all that would be needed to speed things up substantially would be an option to tell the TestBed to reuse the aotSummaries for subsequent spec methods instead of clearing and recreating them for each spec method.

Shireilia commented 6 years ago

For anyone that might be interested : with the release of angular 5, i was finally able to move from Karma to Jest. The results for 3250 tests on our monorepo is : Karma : 12 minutes (2 mins effective run time, 10 compilation) full run Jest: 126 secs full run

In addition to that, i've installed the vscode plugins to runs tests on the fly in the editor one by one. This is such a massive gain that i can't even begin to say how happy i am about it :)

And i think that using @vvasabi workaround i might get even better results. Going to give it a try.

Thanks you so much @thymikee for pointing me to Jest. If anyone wants more info about this, feel free to contact me :)

vikerman commented 6 years ago

We should create a sample repo with best practices for test setup - Having a template cache or setting up AOT summaries.

brian428 commented 6 years ago

@vikerman That would be awesome. It's probably not all that complicated, but since it's undocumented, I just have no idea how to actually do anything with the AOT summaries.

blackholegalaxy commented 6 years ago

@vikerman @Shireilia could you provide some example somewhere on how to get JEST and maybe combined with @vvasabi hack which could be a starting point for good speedy testing practice? We struggle to get a proper solution to work and could be interested to have a look at the way you were able to get it done.

Shireilia commented 6 years ago

@blackholegalaxy sure, i still have to implement @vvasabi hack (business requirement took too much time lately), but got Jest wroking at least. I don't know if i'll have the time to setup a repo in the comming days, but i can share our configuration here as a quick start.

We're using https://github.com/gdi2290/angular-starter as a starter. Nothing much changed exept for how we handle translations (should not have any impact).

Also, make sur to read and follow @thymikee article on the topic : https://www.xfive.co/blog/testing-angular-faster-jest/ wich is the one i used to make things work. The files described in his post are the one i'll put below.

First, we didn't touch the test other than fixing some import statements that were uncorrect, so if you're using karma + jasmine already you should not have to change anything in them. That's important, because @thymikee recommend to change some things regarding the matchers and spys, but we didn't have to do it. So, at first, i'd really recommend to leave your test files untouched.

In our package json, i added this rule:


"jest": {
    "globals": {
      "ts-jest": {
        "tsConfigFile": "src/tsconfig.spec.json"
      },
      "__TRANSFORM_HTML__": true
    },
    "preset": "jest-preset-angular",
    "setupTestFrameworkScriptFile": "/src/setupJest.ts",
    "transformIgnorePatterns": [
      "/node_modules/(?!@ngrx|@VENDOR/FRAMEWORK-NAME)"
    ],
    "testRegex": "(/__tests__/.*|\\.(spec))\\.(ts|js)$"
  }
Most of the problems i had to get it work was because we have a custom framework (services implementing the hypermedia pattern for our apps mostly, but also some form validators, various utils for logs, store management etc...) and had to add it in the transformIgnorePatterns:

 "/node_modules/(?!@ngrx|@VENDOR/FRAMEWORK-NAME)"
Remove what's unecessary in the regex :) For a little bit more input, we had to follow the best practices on how we managed it, and that's providing a compiled version, not the .ts files of the framework. After that, everything went nice :) **Our jestGlobalMocks.ts file:**

import 'rxjs/add/observable/of';
import 'rxjs/add/observable/throw';
import 'rxjs/add/observable/empty';
import 'rxjs/add/observable/forkJoin';
import 'rxjs/add/observable/timer';
import 'rxjs/add/observable/fromEvent';
import 'rxjs/add/observable/merge';
import 'rxjs/add/observable/interval';
import 'rxjs/add/observable/combineLatest';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/share';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/take';
import 'rxjs/add/operator/combineLatest';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/delay';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/skip';
import 'rxjs/add/operator/last';
import 'rxjs/add/operator/finally';
import 'rxjs/add/operator/takeWhile';
import 'rxjs/Observable';
import 'rxjs/Subject';

const mock = () => {
  let storage = {};
  return {
    getItem: (key: any) => key in storage ? storage[key] : null,
    setItem: (key: any, value: any) => storage[key] = value || '',
    removeItem: (key: any) => delete storage[key],
    clear: () => storage = {},
  };
};

Object.defineProperty(window, 'localStorage', { value: mock() });
Object.defineProperty(window, 'sessionStorage', { value: mock() });
Object.defineProperty(window, 'getComputedStyle', {
  value: () => ['-webkit-appearance']
});
Object.defineProperty(document.body.style, 'transform', {
  value: () => {
    return {
      enumerable: true,
      configurable: true
    };
  },
});
We centralised the import for RXJS in this file, will do better in the future ;) **setupJest.ts:**

import 'jest-preset-angular';
import './jestGlobalMocks';
**tsconfigs.spec.json:**

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "sourceMap": true,
    "skipLibCheck": true,
    "noEmit": true,
    "noEmitHelpers": true,
    "importHelpers": true,
    "strictNullChecks": false,
    "allowUnreachableCode": false,
    "allowUnusedLabels": false,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "removeComments": true,
    "noImplicitAny": true,
    "suppressImplicitAnyIndexErrors": true,
    "lib": [
      "dom",
      "es6"
    ],
    "baseUrl": "./src",
    "paths": {
      "@angular/*": [
        "node_modules/@angular/*"
      ]
    },
    "typeRoots": [
      "node_modules/@types"
    ],
    "types": [
      "hammerjs",
      "jest",
      "node",
      "source-map",
      "uglify-js",
      "webpack",
      "fuse"
    ]
  },
  "exclude": [
    "node_modules",
    "dist"
  ],
  "awesomeTypescriptLoaderOptions": {
    "forkChecker": true,
    "useWebpackText": true
  },
  "compileOnSave": false,
  "buildOnSave": false,
  "atom": {
    "rewriteTsconfig": false
  }
}
Our actual jest version numbers :

"@types/jest": "22.1.1",
"jest": "22.2.1",
"jest-preset-angular": "5.0.0",
And that should be it. P.S : we're using Angular 5+.
anjmao commented 6 years ago

I think the problem is that initTestEnvironment is called on every tests run so if you are on watch mode the only good way would be to cache compiled templates. Angular currently is not exposing cache https://github.com/angular/angular/blob/master/packages/compiler/src/jit/compiler.ts#L41

If it would be possible to give custom cache store implementation for testing then we could store cache on global karma object something like this.

__karma__.compiledTemplateCache = __karma__.compiledTemplateCache || new Map<Type, CompiledTemplate>();

@Injectable()
class TemplateCacheStore {
  getTemplateCache(meta) {
    // ... get from global __karma__.compiledTemplateCache
  }

  setTemplateCache(meta) {
    // set to global __karma__.compiledTemplateCache
  }
}

getTestBed().initTestEnvironment(
  BrowserDynamicTestingModule.configure({ templatesCacheStore: TemplateCacheStore }),
  platformBrowserDynamicTesting()
);
brian428 commented 6 years ago

The hack by @vvasabi sounds like it would work, at least in some situations. But going through and changing hundreds of spec files would be very time consuming. And there's no telling how changes to future versions of Angular will break the workaround.

The bottom line is that needing to apply hacks like that is really just absurd. It simply shouldn't take 10 minutes to run UI unit tests. It basically cancels out the whole point of TDD.

What's even more frustrating is that all of these problems would be nullified if we could just tell Angular to re-use the AoT summaries and/or re-use the compiled test module. This issue has been ongoing for over 18 months, and it's baffling to me that the Angular folks just seem to be ignoring it.

I don't understand all the inner workings of the TestBed and the test compiler, but from what I can tell, this should be pretty easy to do for someone who actually understands the internals. Angular seems to already create AoT summaries in memory, so exposing a flag to cache and reuse them within a spec file instead of re-creating them each time seems like a straightforward change.

@vikerman was any further thought put into documentation or examples for using a template cache or re-using the AoT summaries?

ollwenjones commented 6 years ago

I agree... I feel validated by the fact that this issue isn't just affecting us because of some mistake of mine, and it's encouraging to see the community put forth work-arounds, but also disappointing that we're left with implementing hacks with no road-map from core team members... I basically gave up trying to get my team to write unit tests for components... hopefully integration tests will serve us well.

michaelbromley commented 6 years ago

I've made a feature request at #22797 which addresses this issue too.

It would:

Please take a look and provide feedback, since I think this would vastly improve the testing experience of Angular apps.

brian428 commented 6 years ago

@michaelbromley This is a great idea and I love it, but it seems like a parallel option rather than a real fix for the underlying problem (that the components are recompiled over and over again when they don't need to be). Regardless, I'm on board with the mocking idea 100%.

brian428 commented 6 years ago

Also just to show how solvable this problem seems to be, I set up a global function that is called in the beforeAll() block of all of my tests. It does a stupid, ridiculous hack to replace the logic in resetTestingModule().

I wouldn't recommend that anyone actually use this, but it seems to work fine and it speeds things up by nearly 40%. My point isn't to offer a solution for anyone to use, but simply to show how easy it seems to be to vastly speed things up just by reusing the compiled components:

let tb: any = getTestBed();

// Switch back to original resetTestingModule() function if it exists
if( ( window as any )[ "__testCache" ] ) tb.resetTestingModule = ( window as any )[ "__testCache" ].realResetTestingModule;

// Do a reset on the testing module.
TestBed.resetTestingModule();

// Store reference to original resetTestingModule() function.
let realResetTestingModule = tb.resetTestingModule;
( window as any )[ "__testCache" ] = { realResetTestingModule: tb.resetTestingModule };

// Replace original resetTestingModule() with a custom version that re-uses the moduleFactory and compiler.
// This cuts the test execution time by roughly 40%.
tb.resetTestingModule = () => {
  let mf = tb._moduleFactory;
  let compiler = tb._compiler;
  realResetTestingModule.apply( tb );
  tb._moduleFactory = mf;
  tb._compiler = compiler;
};
ChrisGibb commented 6 years ago

@vmandy (I know I'm late) .. but facing this problem of slow unit, out of memory errors as well

The two things I did to get around this are:

1) All variables that are used in beforeEach, are declared at the top of the file inside the first describe. e.g.

describe("FooComponent", () => {
 let var0: TypeFoo;
 let var1: TypeFoo;
 let var2: TypeFoo;

 beforeEach(() => {
   var0 = // something big
   var1 = // something big
   var2 = // something big
 })
 afterEach(() => {
   var0 = undefined
   var1 = undefined
   var2 = undefined
 })

 // my other 'describe', 'beforeEach' and 'it' blocks afterwards
});

This will fixed my out of memory problems.

2) To speed things up. Replace all your IonicModule bits with NO_SCHEMA_ERRORS e.g:

Replace:

imports: [
 IonicModule.forRoot(component),
]

with:

schemas: [
 NO_ERRORS_SCHEMA
]

And you're all good!

kedar9444 commented 6 years ago

@vvasabi @BurningDog @brian428 : is this hack still works with angular 5.1.3? because i have configured the test suit same way and able to execute the single spec file. but when i try to execute all the spec files, the first one get passed and all other test cases get failed with following error.

Failed: Uncaught (in promise): Error: Illegal state: Could not load the summary for directive AccountDetailsComponent.

if not what are possible workarounds available for me, because our current test suit taking around 10 to 12 min to execute the test cases and if we multiple this number with head count of resources we are spending to much time just to execute the test cases.

vvasabi commented 6 years ago

@kedar9444 My project is on Angular 5.2.0, and I have not had to change the way I implemented my hack since I originally published it last year. If you could share some of your code that reproduces the issue, maybe people can take a look.

kedar9444 commented 6 years ago

@vvasabi : thanks for responding i can breath now!!! lets see what i have done in my code.

I have created one configuration file for all spec files :


export const configureTestSuite = () => {
    const testBedApi: any = getTestBed();
    const originReset = TestBed.resetTestingModule;

    TestBed.resetTestingModule();
    TestBed.resetTestingModule = () => TestBed;

    afterEach(() => {
        testBedApi._activeFixtures.forEach((fixture: ComponentFixture<any>) => fixture.destroy());
        testBedApi._instantiated = false;
    });

    afterAll(() => {
        TestBed.resetTestingModule = originReset;
        TestBed.resetTestingModule();
    });
}

then used the same in actual test case :


describe('Component: xxxComponent', () => {
    configureTestSuite();

    beforeAll(done => (async () => {

        TestBed.configureTestingModule({
            imports: [SharedModule, BrowserModule...],
            declarations: [xxxComponent, yyyComponent],
            schemas: [CUSTOM_ELEMENTS_SCHEMA],
            providers: [
                { provide: xxxService, useClass: xxxServiceStub }
                ...
            ],

        })

        await TestBed.compileComponents();

    })().then(done).catch(done.fail));

    describe('Component: templateFormComponent', () => {

        let xxxfixture: ComponentFixture<xxxComponent>;
        let xComponent: xxxComponent;

        let yyyfixture: ComponentFixture<yyyComponent>;
        let yComponent: yyyComponent;

        let xService: xxxService
        let el: HTMLElement;

        beforeEach(() => {

            xxxfixture = TestBed.createComponent(xxxComponent);
            xComponent = xxxfixture.componentInstance;

            xService = TestBed.get(xxxService);
        });
    });
});

with this implementation if i tried to run any single spec file [with fdescribe] it gives proper result, but if i tried to run all the spec files in one go the configuration runs the first spec file correctly but through error for all other spec files.

and the error is :

Failed: Uncaught (in promise): Error: Illegal state: Could not load the summary for directive xxxComponent.

if you need more data please mention in comment. thanks in advance. :)

here are some more details:


Angular CLI: 1.7.4
Node: 9.11.1
OS: win32 x64
Angular: 5.2.4
... animations, common, compiler, compiler-cli, core, forms
... http, language-service, platform-browser
... platform-browser-dynamic, router

@angular/cli: 1.7.4
@angular-devkit/core: 0.0.29
@schematics/package-update: 0.3.2
typescript: 2.4.2
webpack: error
kedar9444 commented 6 years ago

here is the code to reproduce the issue.

https://github.com/kedar9444/unit-test-performance

vvasabi commented 6 years ago

@kedar9444 The Github repo was helpful. All I did to fix your issue was to have this in configure-test-suit.ts:

beforeAll(() => {
  TestBed.resetTestingModule();
  TestBed.resetTestingModule = () => TestBed;
});

You need those 2 lines in a beforeAll block. Hope this helps.

sonicoder86 commented 6 years ago

After adding the beforeAll part I get this error: Cannot configure the test module when the test module has already been instantiated. I call configureTestingModule in my beforeEach hooks.

vvasabi commented 6 years ago

@blacksonic The idea behind this hack is that time is saved by not having to call configureTestingModule once per it block to recompile components. As such, the complaint that the test module has already been instantiated will go away if you only call configureTestingModule once per the outermost describe block.

oocx commented 5 years ago

I created a pull request for this problem last year but never got any feedback from the Angular team. (#17710). We are using code very similar to that pull request at my employer in an Angular app with more than 1.000 unit tests.

giggio commented 5 years ago

Testing in Angular is really problematic right now because of this performance issue. We are also on a 1000+ tests app, and because of this hack we were able to go from about 30 minutes to about 3 minutes for a full test run. This is really bad, a 30m test run is unacceptable. And even 3m is bad for unit tests for just 1000+ tests.

The problem lies on all the rendering and the dependency on a browser. I hope angular switches to something like JSDom ASAP (and away from Karma and Jasmine), otherwise tests will never be as fast as they have to be.

vvasabi commented 5 years ago

@giggio You can already use JSDom with Angular unit tests via Jest. Look for thymikee’s comments in this thread.

awerlang commented 5 years ago

The problem isn't in the real vs fake DOM approach. jsdom-based tests with Jest can cut running time to half. Still too slow for even a dozen tests. I'm afraid Jest has still a long way to go to take advantage of parallelism & fake DOM which in theory looks much better than current implementations. The community would benefit from this of course.

To achieve a tenfold decrease in running time Angular needs to allow the testing module to be configured in a beforeAll() block. Right now it's just naively discarding it after each test. This should be prioritized until caching compilation of sorts takes place. We don't need the best solution now, but we deserve a non-naïve solution at least until something better is created.

So do we want to have a 2x or 10x speedup? And how long are we willing to wait?

mattem commented 5 years ago

Something that has worked well for us when testing our component library is splitting it up into smaller libraries where possible, then running these library tests in parallel on CI nodes, while also using Karma plugins such as karma-parallel to parallelize even further.

That, coupled with overriding of the resetTestingModule and managing the reset manually as previously mentioned in this thread, has helped reduce times across 6000+ specs enough while we wait for a Bazel / caching build based solution.

blackholegalaxy commented 5 years ago

we should'nt have to rely on CI tricks to speedup the tests. I think @awerlang is right: Angular should act in a way the tests are ran properly without rebilding the whole component if the setup can be consistent between the tests.

brian428 commented 5 years ago

The frustrating thing about this is that, as I showed earlier in this thread, the logic to simply hold onto the compiled test module is trivial. I just have no idea how to "properly" change the actual Angular code within TestBed or Compiler to allow this, or I'd have sent a pull request long ago.

I realize that there are probably edge cases where my solution won't work (e.g. where individual specs are changing/overriding different parts of the test module), but having a flag to turn the compilation result caching on or off would also be a simple addition.

I'm really just totally baffled that the problem has been this bad for this long, yet no one on the team seems to be prioritizing this.

abeymg commented 5 years ago

Any update on this? Is any effort being made to speed up test execution when using the TestBed ? How do larger projects at Google that use Angular deal with the slow tests ?

kyliau commented 5 years ago

I've just LGTM @oocx https://github.com/angular/angular/pull/17710. In Google, tests are done with AOT by providing the NgSummaries to TestModuleMetadata This is the recommended way to get the best performance, but we understand that it might not be easy to get hold of the NgSummary files. With Ivy coming out soon AOT will be the default. In the meantime, please try out the suggestions in the PR.

mattem commented 5 years ago

@kyliau Do you have any examples of passing AOT summaries here?

abeymg commented 5 years ago

@kyliau Thanks for the response. I am already using the one of the workarounds mentioned earlier in this thread. My question was more about when the official support for this would land in the core. I'll follow the PR to see when it makes it out.

Question about what you mentioned about Ivy. With Ivy making AOT the default, does the workflow for tests using TestBed change?

alex-kel commented 5 years ago

Hello everyone! I am trying to implement this approach with configureTestSuite function. Did someone face issue when with this approach Karma says that I am running 'Empty test suite'?

Here the test code - https://gist.github.com/alex-kel/e549a845e37b00678a17da7340432683

@awerlang @vvasabi probably you guys can help me?

kekel87 commented 5 years ago

Hello, In my team, we just used ng-bullet to accelerate the tests of our different projects. This allowed impressing gains (ex: 2m30s to 30s), without any problem for the moment.

alex-kel commented 5 years ago

I tried using ng-bullet but still getting the same “Empty test suite” message... сб, 29 сент. 2018 г. в 13:47, Michael notifications@github.com:

Hello, In my team, we just used ng-bullet https://www.npmjs.com/package/ng-bullet to accelerate the tests of our different projects. This allowed impressing gains (ex: 2m30s to 30s), without any problem for the moment.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/angular/angular/issues/12409#issuecomment-425635583, or mute the thread https://github.com/notifications/unsubscribe-auth/AGshdnkc_522rtrlJCtn2TmcvZSlN4wAks5uf0_EgaJpZM4KcS5I .

ollwenjones commented 5 years ago

@kekel87 that's brilliant that someone took on that problem with a tidy library API! When I opened the issue, I never dreamed it would come 2 years later from a 3rd party, though.

cyungmann commented 5 years ago

So @kyliau, with Ivy now seemingly delayed, can you give some guidance on how to provide the NgSummaries? Is there some example code for this? Does it still work with v7?

Thanks, Chris

ersimont commented 4 years ago

I've put together a little helper to re-use compilation results for given modules for all your tests. I'd love to hear if it helps others. It's the precompileForTests() function in s-ng-dev-utils.

It seems like the APIs available for things like this keep changing, so we'll see how long this can last. I created this with Angular 8. It sounds like better support for AoT in tests may be coming with Ivy? So maybe this solution won't need to last long! 🤞

FrancescoBorzi commented 4 years ago

This looks an important issue, and becomes more critical as your modules grow.

Ideally it should just be possible to run the TestBed.configureTestingModule inside a beforeAll.

That would be the most elegant solution.

davidjpfeiffer commented 4 years ago

The blog post below explains how to skip the recompilation step so that you can use real dependency injection, compile the component once, and then run all the tests. Thank you to Nikita Yakovenko for sharing this solution!

https://blog.angularindepth.com/angular-unit-testing-performance-34363b7345ba.

kekel87 commented 4 years ago

Yep, @davidjpfeiffer, as I mentioned above, this blog post update speak about ng-bullet.

Moreover almost all the projects of my teams have been there for almost a year, and it still works very well! Combined with ng-mock, It really does good jobs !

DenysVuika commented 4 years ago

What is more disturbing is that this problem with TestBed has been there for years already.

MatMercer commented 4 years ago

We have more than 600 tests in our project and it takes more than 5 minutes to execute everything. Or 3 minutes to start a simple test. I agree with @brian428 "It basically cancels out the whole point of TDD.".

Working this way is very very bad.

Goodwine commented 4 years ago

@MatMercer something that really helped me was a "recent" Angular update that lets you specify the flag --include, so during development I set up something like:

git diff HEAD^ --name-only | xargs -L 1 dirname | sort | uniq etc and eventually got the output to be something like {dir1/a/b/*.ts,dir2/a/c/*ts} which I passed to ng test --include ${test_glob?}.

It's ugly, but you can always call ng test --include dir/i/am/working/on/ too

I was able to move faster (especially combined with our fork of ng-bullet). And like others have mentioned, karma-parallel also helped bring down 4k tests from 20min to 5min on the full test suite

MatMercer commented 4 years ago

@Goodwine I'm currently using angular 5.2.0. ng-bullet for some reason causes problems with npm (the o'l peer dependencies problem). About the karma-parallel, it only bring it down from 5:20 to 4:40, not a huge difference. I tried using your suggestion, but since you already said

what really helped me was a "recent" Angular update

Since I'm using 5.2.0, I don't have the "recent" features.

About the other solutions presented here, I can't use them, since the tests changes the components/use spy a lot. I really don't know what else to do from here. Maybe upgrade the Angular version and pray for it to work?

giniedp commented 4 years ago

you dont need the ng-bullet dependency. The implementation is pretty simple.

this is what we use in our project

import { getTestBed, TestBed } from "@angular/core/testing"

export function configureTestSuite(configureAction: () => Promise<any>) {
  const testBedApi = getTestBed()
  const originReset = TestBed.resetTestingModule
  beforeAll(() => {
    TestBed.resetTestingModule()
    TestBed.resetTestingModule = () => TestBed
  })
  if (configureAction) {
    beforeAll((done) => {
      configureAction().then(done).catch(done.fail)
    })
  }
  afterEach(() => {
    testBedApi["_activeFixtures"].forEach((fixture) => fixture.destroy())
    testBedApi["_instantiated"] = false
  })
  afterAll(() => {
    TestBed.resetTestingModule = originReset
    TestBed.resetTestingModule()
  })
}

with that you have no peer dependency problems. Further you are in control of the behavior so you can revert it at any time, without changing your tests.

Goodwine commented 4 years ago

@MatMercer

Since I'm using 5.2.0, I don't have the "recent" features.

Be aware that angular versions are only supported for a limited amount of time [docs] And also, if the Angular team improves the test setup performance like you want, it would be on a newer version, so you would have to upgrade anyways, there's really not workaround*.

On another Issue about something similar, someone mentioned that the next Angular release with Ivy and R3SomethingTestBed made test setup run much faster even without AOT. https://github.com/angular/angular-cli/issues/6650

(Disclaimer: I'm not a member of Angular team)