Description
Hi,
This issue is related to this thread.
I'm writing a test for a form wizard using Angular Testing Library, and my approach involves splitting each test case using the it
or describe
methods. To avoid mocking the user session and other application parts, I render the <app-root></app-root>
component in beforeAll
. This allows me to lead my test to the wizard and test each step.
However, Angular clears/destroys the component/view/app after each it
call. This means that if I move all preparations (login, navigation to the wizard, etc.) to the first it
and start testing the wizard in the next calls, the whole progress will be lost, and the view will contain only the body
tag.
Thanks to @timdeschryver, I could avoid this behavior and turn off view destroying after each it
call using the next configuration:
configureTestBed: (testBed): void => {
testBed.configureTestingModule(
{
teardown: { destroyAfterEach: false },
},
);
},
But it looks like this configuration breaks change detection because nothing happens if I enter any data into the form or want to navigate.
Here's what my test looks like:
import { AppComponent } from './app.component';
import { RenderResult, render, screen } from '@testing-library/angular';
import { APP_ROUTES } from './app.routes';
import { userEvent } from '@testing-library/user-event';
describe('AppComponent', () => {
const user = userEvent.setup();
let component: RenderResult<AppComponent>;
beforeAll(async () => {
component = await render('<app-root></app-root>', {
imports: [
AppComponent,
],
routes: APP_ROUTES,
// This configuration keeps the component instance and render results between tests
// But breaks change detection
configureTestBed: (testBed): void => {
testBed.configureTestingModule(
{
teardown: { destroyAfterEach: false },
},
);
},
});
component.detectChanges();
});
it('should render loginBtn', () => {
const loginBtn: HTMLButtonElement = component.getByText('Login').closest('button')!;
expect(loginBtn).toBeTruthy();
});
it('should disable login button by default', () => {
const loginBtn: HTMLButtonElement = component.getByText('Login').closest('button')!;
expect(loginBtn.disabled).toBeTruthy();
});
it('should enter username value', async () => {
const usernameInput: HTMLInputElement = screen.getByLabelText(/Username/i);
await user.type(usernameInput, 'John Doe');
expect(usernameInput.value).toBe('John Doe');
});
it('should have username value entered before, enter password and enable button', async () => {
const usernameInput: HTMLInputElement = screen.getByLabelText(/Username/i);
const passwordInput: HTMLInputElement = screen.getByLabelText(/Password/i);
// Have username value entered before
expect(usernameInput.value).toBe('John Doe');
await user.type(passwordInput, 'mysuperpassword');
expect(passwordInput.value).toBe('mysuperpassword');
const loginBtn: HTMLButtonElement = component.getByText('Login').closest('button')!;
// This test is failing because the change detection is not working when teardown.destroyAfterEach is set to false
expect(loginBtn.disabled).toBeFalsy();
});
});
I've created a repo with minimal reproduction code (not from a real app) and left a few comments there.
The main goal I want to achieve is to have one big test for each functionality in my app but be able to split each such test by test cases using it
and describe
to have better readability and maintenance.
P.S. @timdeschryver also suggested using ATL_SKIP_AUTO_CLEANUP
, but it doesn't work.