Alright, let's break this down. First things first, I was missing certain configurations for the initTestEnvironment
that were introduced in the middle of 2022.
import 'jest-preset-angular/setup-jest';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting,
} from '@angular/platform-browser-dynamic/testing';
getTestBed().resetTestEnvironment();
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting(),
{
teardown: { destroyAfterEach: false },
errorOnUnknownElements: true, // <---
errorOnUnknownProperties: true, // <---
}
This configuration ensures that any unknown components or template errors will trigger test failures as they would during a build process. However, there are still other types of errors that may cause runtime or build failures not covered by these properties. To address those, I implemented a custom override for console.error to throw an error instead of just logging it. You can add this to your test-setup.ts file:
// test-setup.ts
import { failTestOnConsoleError } from './testing-utils'
...
getTestBed().resetTestEnvironment();
getTestBed().initTestEnvironment(
...
);
beforeEach(() => {
failTestOnConsoleError();
});
// testing-utils.ts
const defaultConsoleError = window.console.error;
/**
* Forces a Jest test to fail when a console.error is detected.
*
* To allow the test to pass, execute {@link permitTestWithConsoleError} in your test.
*/
export function failTestOnConsoleError(): void {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
window.console.error = (...args: any[]) => {
defaultConsoleError.apply(this, args);
throw new Error(
'Test was forced to fail due to a console.error that was triggered. If this console.error should be permitted, execute permitTestWithConsoleError() in your test.'
);
};
}
/**
* Allows a Jest test to pass even if a console.error is detected.
*
* This is the default behavior and undos the logic in {@link failTestOnConsoleError}
*/
export function permitTestWithConsoleError(): void {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
window.console.error = (...args: any[]) => {
defaultConsoleError.apply(this, args);
console.warn('Test permitted to pass despite throwing a console.error.');
};
}
By calling failTestOnConsoleError()
in the beforeEach
of your test-setup, any test sending a console.error will result in a failed test. On the other hand, using permitTestWithConsoleError()
will allow tests to throw console.errors and still pass, reverting back to default behavior.
// some-random.spec.ts
describe('my tests', () => {
it('will pass', () => {
... // Expecting no errors, so fail the test if any occur
});
it('will have error', () => {
permitTestWithConsoleError(); // Testing specifically for an error, so the test shouldn't fail upon detection
myMockedService.myMethod.mockImplementationOnce(() =>
throwError(() => new Error('oh no I failed'))
);
...
});
});