Skip to main content

How to update to the latest version of ng-mocks

Usually, you can use the latest version of ng-mocks with any Angular 5+ application.

Below you can find critical changes. Most of them happen on major releases. Bug fixes which can affect tests that relied on the previous behavior are listed too.

If you are facing an issue, despite the instructions, please, feel free to contact us.

From ng-mocks 14.15 to 14.16​

Signal inputs of mocked components and directives​

Starting with 14.16.0, MockComponent and MockDirective preserve inputs declared with input() or input.required() as Angular signals. This also applies to components and directives mocked by MockBuilder, including dependencies mocked indirectly through their modules. Previously, a bound signal input on a mocked declaration was incorrectly replaced with its plain value. The change fixes #13671 and makes mocked and kept declarations expose the same input interface. Input aliases are preserved too.

This is an observable behavior change for Angular 17+ tests which relied on the old mock-only behavior. For example, this assertion could pass before 14.16.0 even though name is declared as a signal:

const child = ngMocks.findInstance(ChildComponent);

expect(child.name).toEqual('test');

Since 14.16.0, read the signal as you would on the real component or directive:

const child = ngMocks.findInstance(ChildComponent);

expect(child.name()).toEqual('test');

Before updating, check tests which access mocked components through componentInstance and mocked components or directives through ngMocks.findInstance or ngMocks.get. If a property is declared with input() or input.required(), update direct reads to call the signal. Do not assign a value directly to the signal property. Change the value bound by the parent or host template and run change detection.

For example, suppose HostModule imports a module declaring DependencyDirective, and the host's span binds [publicLabel]="label" to the directive's aliased label signal. MockBuilder mocks that directive as a dependency:

beforeEach(() => MockBuilder(HostComponent, HostModule));

it('updates the mocked directive input through the host binding', () => {
const fixture = MockRender(HostComponent);
const host = fixture.point.componentInstance;
const element = ngMocks.find('span');
const directive = ngMocks.get(element, DependencyDirective);

host.label = 'label-second';
fixture.detectChanges();

expect(directive.label()).toEqual('label-second');
expect(ngMocks.input(element, 'publicLabel')).toEqual('label-second');
});

The same reads and host-binding updates work with a directive created directly by MockDirective. For a host created with TestBed.createComponent in a zoneless test, call fixture.changeDetectorRef.markForCheck() after direct property writes and before fixture.detectChanges(). See Angular's zoneless testing guidance.

For components rendered directly, Angular's ComponentRef.setInput is another way to update an input; run change detection afterwards. Directives do not have a ComponentRef, so update their host bindings instead.

For value-oriented assertions which should work both before and after 14.16.0, use ngMocks.input. It returns the input's value for both components and directives. Use the public binding alias when an input has one, as with publicLabel above:

const child = ngMocks.find(ChildComponent);

expect(ngMocks.input(child, 'name')).toEqual('test');

No changes are needed for decorator-based @Input() properties; they remain ordinary properties. Kept components and directives already exposed signal inputs as signals, so this migration applies only when the declaration is replaced with a mock.

Preserving the signal interface does not preserve the original constructor or field initializers. For example, do not expect a mocked input('default') to retain that initial value. Supply the values needed by the test through its bindings.

This also affects transforms defined in an input() or input.required() initializer. Angular does not expose those transform functions in reflected input metadata, so ng-mocks cannot recover them without running the original initialization. A mocked component or directive therefore receives the raw bound value: a bound string such as '2' remains a string, even if the original signal input transforms strings into numbers. Decorator-based @Input({ transform: ... }) transforms are preserved through their reflected metadata.

When a test needs the original signal-input transform or initialization, keep that component or directive. For the directive example above, replace the setup with:

beforeEach(() =>
MockBuilder(HostComponent, HostModule).keep(DependencyDirective),
);

From 21 to 22​

There are no special cases. The update should be straight forward.

From 20 to 21​

There are no special cases. The update should be straight forward.

From 19 to 20​

There are no special cases. The update should be straight forward.

From 18 to 19​

There are no special cases. The update should be straight forward.

From 17 to 18​

There are no special cases. The update should be straight forward.

From 16 to 17​

There are no special cases. The update should be straight forward.

From 15 to 16​

There are no special cases. The update should be straight forward.

From 14 to 15​

test.ts​

If you want to use global configuration and features like ngMocks.autoSpy, you need to add test.ts back to your project: How to add ng-mocks to a fresh Angular 15 project

RouterOutlet​

If you have been testing RouterModule using MockRender(RouterOutlet), you need to add empty params to MockRender:

const fixture = MockRender(RouterOutlet, {});

From 13 to 14​

MockBuilder becomes stricter and starts to throw errors on wrong configuration. If you call MockBuilder with 2 parameters and use the chain for dependencies:

beforeEach(() => {
return MockBuilder(Declaration, ItsModule)
.keep(Dep1)
.mock(Dep2);
});

MockBuilder throws an error if Dep1 or Dep2 hasn't been imported or declared somewhere in ItsModule and its imports.

important

That has been done to let you know when one of dependencies is missing. So, if a developer has removed Dep2 from ItsModule you would get an error during CI instead of production.

It's not recommended, but you can change this to console.warn or disable it. For that, please change the config of ng-mocks in src/test.ts, src/setup-jest.ts or src/test-setup.ts:

ngMocks.config({
onMockBuilderMissingDependency: 'warn', // or 'i-know-but-disable'
});

Usually, you need Dep1 or Dep2 which aren't imported in ItsModule, when they are external dependencies, such as MatDialogRef or ActivatedRoute. In this case, please add them explicitly to the params of MockBuilder instead of chain methods:

beforeEach(() => {
return MockBuilder(
// Things to keep and export.
[Declaration, Dep1, MatDialogRef], // providing and keeping MatDialogRef
// Things to mock and export.
[ItsModule, Dep2, ActivatedRoute], // providing and mocking ActivatedRoute
);
});

Please note, that if you call MockBuilder with 0 or 1 parameters, all chained dependencies are added to TestBed and exported by default now:

// It doesn't throw, allows access to Declaration, MatDialogRef, Dep2 and ItsModule in TestBed.
beforeEach(() => {
return MockBuilder(Declaration)
.mock(ItsModule)
.keep(MatDialogRef)
.mock(Dep2);
});

// It doesn't throw, allows access to Declaration, Dep1, ActivatedRoute and ItsModule in TestBed.
beforeEach(() => {
return MockBuilder()
.keep(Declaration)
.mock(ItsModule)
.keep(Dep1)
.mock(ActivatedRoute);
});

From 12 to 13​

There are no special cases. The update should be straight forward.

From any old one to 12.4.0​

Because of issues with the speed of merging a fix for jest, there is a braking change in 12.4.0.

If you are using MockInstance in beforeAll, beforeEach or it, and rely on automatic reset, then you have to perform extra configuration. More information in the How to install ng-mocks and in MockInstance.scope sections.

From 11 to 12​

The only breaking change is auto-spy.

ngMocks.autoSpy('jasmine') and ngMocks.autoSpy('jest') should be used instead of import 'ng-mocks/dist/jasmine'; and import 'ng-mocks/dist/jest';.

From 11.10 to 11.11 and higher​

If you are facing an issue with MockRender and a thrown error about "Forgot to flush TestBed?", you may want to suppress it instead of fixing, whereas fixing it is the right way.

In order to suppress the error, you need to upgrade to 12.0.1 at least, and to add in test.ts:

ngMocks.config({
onTestBedFlushNeed: 'warn',
});

Then instead of throwing errors, MockRender will log them in console as warnings.

From 10 to 11​

MockModule​

Now it does not export all mock imports and mock declarations, but respects exports of modules. The story is the same as in the update from 8 to 9 for MockBuilder.mock.

If you still need to export them, then you should consider a migration of affected tests to MockBuilder or ngMocks.guts.

MockHelper​

MockHelper has been renamed to ngMocks, please check its docs.

MockComponent​

Previously, it had been accepting a meta parameter, now it has been removed.

Contact us, if you are using this functionality.

Tokens​

From 9 to 10​

There are no special cases. The update should be straight forward.

From 8 to 9​

from MockModule to MockBuilder.mock​

MockModule exports all imports and declarations, and MockBuilder.mock respects exports of modules.

This behavior allows tests to fail, if a declaration of a module has been changed, and it does not export a dependency anymore. Likewise, an Angular application would fail too.

From 7 to 8​

There are no special cases. The update should be straight forward.

From 6 to 7​

There are no special cases. The update should be straight forward.

From 5 to 6​

There are no special cases. The update should be straight forward.