33 KiB
Testing Guidelines
Table of Contents
- Introduction
- Core Testing Principles
- Testing Tools
- Best Practices
- Example Test Structures
- End-to-End (E2E) Testing Attributes (
data-what,data-which) - Running Tests (Nx)
- References
Introduction
This document outlines the guidelines and best practices for writing unit tests in this Angular project. Consistent and effective testing ensures code quality, maintainability, and reliability.
Key Requirements:
- Test files must end with
.spec.ts. - Use Jest as the primary test runner.
- Utilize Spectator for testing Components, Directives, and Services.
- Employ ng-mocks for mocking complex dependencies like child components.
Core Testing Principles
Adhere to these fundamental principles for writing effective unit tests.
Arrange-Act-Assert (AAA) Pattern
Structure your tests using the AAA pattern for clarity and consistency:
- Arrange: Set up the test environment. Initialize objects, mock dependencies, and prepare inputs.
- Act: Execute the code being tested. Call the function or method under scrutiny.
- Assert: Verify the outcome. Check if the actual result matches the expected result.
Isolation: Mocking Dependencies
Unit tests should focus on a single unit of code in isolation.
- Mock External Dependencies: Services, APIs, or other modules the unit interacts with should be mocked to prevent side effects and external failures from impacting the test. Spectator's
mocksarray in factory options simplifies this. - Mock Child Components: When testing a component, mock its child components using Spectator's
overrideComponentsto ensure you are only testing the parent component's logic and template, not the children's implementation details.
Error Case Testing
Go beyond the "happy path". Ensure your tests cover:
- Edge cases
- Invalid inputs
- Error scenarios and how the unit handles them
Clarity and Isolation
- Clear Names: Write descriptive
describeanditblock names that clearly state what is being tested and the expected outcome. - Independent Tests: Ensure tests do not depend on each other. Each test should set up its own state and clean up afterward. Avoid relying on the execution order of tests.
Testing Tools
Jest: The Test Runner
Jest is the testing framework used for running tests, providing features like test discovery, assertion functions (expect), and mocking capabilities (jest.fn(), jest.spyOn()).
Spectator: Simplifying Angular Tests
Spectator significantly reduces boilerplate and simplifies testing Angular components, directives, and services.
Spectator Overview
- Less Boilerplate: Streamlines
TestBedconfiguration. - Easy DOM Querying: Provides intuitive methods (
query,queryAll,byText, etc.) to find elements. - Clean Event API: Simplifies triggering user interactions (
click,typeInElement,keyboard). - Custom Matchers: Offers Jest matchers (
toExist,toHaveText,toBeDisabled, etc.) for more readable assertions.
Core Factory Methods
Spectator provides factory functions to set up the testing environment:
createComponentFactory: For testing components in isolation.createHostFactory: For testing components within a host template (useful for inputs/outputs).createDirectiveFactory: For testing directives.createServiceFactory: For testing services.createHttpFactory: For testing services that interact withHttpClient.
Example Usage (Component):
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
import { MyComponent } from './my-component.component';
import { SomeService } from '../some.service';
const createComponent = createComponentFactory({
component: MyComponent,
mocks: [SomeService], // Automatically mocks SomeService
detectChanges: false, // Control initial change detection
});
let spectator: Spectator<MyComponent>;
beforeEach(() => spectator = createComponent());
Querying Elements
Use Spectator's query methods to find elements for interaction or assertion:
// CSS selectors
const button = spectator.query('button.submit');
const inputs = spectator.queryAll('input');
// By component/directive type
const child = spectator.query(ChildComponent);
// By text, label, placeholder, etc.
const submitBtn = spectator.query(byText('Submit'));
const usernameInput = spectator.query(byLabel('Username'));
Working with Inputs/Outputs
Easily set inputs and subscribe to outputs:
// Set single input
spectator.setInput('user', { name: 'Test' });
// Set multiple inputs
spectator.setInput({ user: { name: 'Test' }, isAdmin: true });
// Host component input
// spectator.setHostInput('propName', value); // If using createHostFactory
// Subscribe to output
const outputSpy = jest.fn();
spectator.output('userLoggedIn').subscribe(outputSpy);
spectator.click('button.login');
expect(outputSpy).toHaveBeenCalled();
Event Triggering
Simulate user interactions:
spectator.click('.login-button');
spectator.typeInElement('test user', 'input[name="username"]');
spectator.keyboard.pressEnter('input[name="password"]');
spectator.selectOption(spectator.query('select'), 'Option 2');
Custom Matchers
Improve assertion readability:
expect('.error-message').not.toExist();
expect('h1.title').toHaveText('Welcome');
expect('input.email').toHaveValue('test@example.com');
expect('button.submit').toBeDisabled();
expect('.parent').toHaveDescendant('.child');
ng-mocks: Advanced Mocking
ng-mocks excels at mocking complex Angular artifacts like Components, Directives, Pipes, and Modules, further enhancing test isolation. Note: While ng-mocks offers powerful features like MockComponent and MockDirective, in a standalone component architecture, the primary way to replace dependencies (including child components or directives) is often through Spectator's overrideComponents feature, as shown in the examples below. ng-mocks might still be useful for features like MockProvider or MockInstance.
ng-mocks Overview
- Deep Mocking: Easily mocks entire modules or specific declarations/providers.
- Boilerplate Reduction: Simplifies complex
TestBedsetups. - Helper Utilities: Provides functions for interactions (
ngMocks.change,ngMocks.click) and querying (ngMocks.find).
Key APIs
MockBuilder: Fluent API to configureTestBed, specifying what to keep real and what to mock.MockRender: EnhancedTestBed.createComponentthat respects lifecycle hooks and simplifies input/output binding.MockComponent,MockDirective,MockPipe,MockProvider: Functions to create shallow mocks of specific artifacts. (Note: For standalone, preferoverrideComponentsfor components/directives).MockInstance: Allows customizing mock behavior before initialization (e.g., setting up spies).
When to Use ng-mocks
- Mocking specific services or pipes using
MockProviderwhen more control is needed than Spectator'smocksarray offers. - When needing fine-grained control over mock behavior using
MockInstance. - Testing components that rely heavily on content projection (
ng-content). - Caution: Avoid
MockComponentandMockDirectivefor standalone components/directives; use Spectator'soverrideComponentsinstead.
Integration with Spectator
Spectator and ng-mocks can work together. Use Spectator for the primary test setup and interaction/assertion. Use Spectator's overrideComponents to replace standalone child components/directives. Use ng-mocks' MockProvider or MockInstance when needed for services or advanced mocking scenarios.
// Example using Spectator's overrideComponents for a standalone child
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
import { ParentComponent } from './parent.component';
import { ChildComponent } from './child.component';
import { MockChildComponent } from './mock-child.component'; // A standalone mock component
import { ComplexService } from './complex.service';
import { MockProvider } from 'ng-mocks'; // ng-mocks can still be used for providers
const createComponent = createComponentFactory({
component: ParentComponent, // Assume ParentComponent imports ChildComponent
overrideComponents: [
[
ParentComponent, // Target the component whose imports need overriding
{
remove: { imports: [ChildComponent] }, // Remove the real standalone component
add: { imports: [MockChildComponent] }, // Add the mock standalone component
},
],
],
providers: [
MockProvider(ComplexService, { // Mock service using ng-mocks' MockProvider
getData: () => of(['mocked data']),
}),
],
});
// ... rest of the test using Spectator API ...
Best Practices
General
- Focus: Each test (
itblock) should ideally test one specific behavior or scenario. - Readability: Use clear variable names and structure tests logically (AAA).
- Avoid Logic: Do not put complex logic within tests. Keep them simple and direct.
- No Implementation Details: Test the public API or observable behavior, not private methods or internal state.
Component Testing
- Use
createComponentFactoryfor most components. UsecreateHostFactoryonly when testing interaction via template bindings (inputs/outputs) is necessary. - Test inputs change behavior as expected.
- Test outputs are emitted correctly upon interaction.
- Test conditional rendering (
@if,@switch) and loops (@for). - Verify important DOM elements are rendered correctly based on state.
- Mock Standalone Child Components/Directives using Spectator's
overrideComponentsfeature to isolate the component under test.
Service Testing
- Use
createServiceFactory. - Mock dependencies using the
mocksarray. - Test public methods, ensuring they return expected values or have the correct side effects (like calling mocked dependencies).
- For services using
HttpClient, usecreateHttpFactoryandSpectatorHttpfor easy request expectation (spectator.expectOne).
Directive Testing
- Use
createDirectiveFactory. - Test how the directive affects the host element or other elements based on inputs or events.
- If a directive has standalone dependencies, use
overrideDirectives(similar tooverrideComponents) in the factory options.
Mocking
- Prefer Spectator's
mocksarray for simple service mocking. - Use
spectator.inject(ServiceToMock)to get the mocked instance and configure its methods (e.g.,mockService.method.mockReturnValue(...)). - Use Spectator's
overrideComponentsoroverrideDirectivesto replace standalone component/directive dependencies with mocks. - Use
MockProviderfromng-mocksif you need more control over service mocking than Spectator'smocksarray provides, or for mocking tokens/values.
Async Operations
- Use
async/awaitwith Promises. - For RxJS Observables, subscribe within the test or use helpers like
waitForAsyncor Jest's timer mocks if needed, though often direct subscription is sufficient. Spectator handles basic async operations well.
Performance
- Mock heavy dependencies (e.g., HTTP calls, complex computations).
- Keep test setup (
beforeEach) lean. UsebeforeAllonly for setup that is truly read-only and shared across all tests in adescribeblock.
Debugging
- Use
spectator.debug()to print the current component's DOM structure to the console. - Use standard
console.logstatements temporarily. - Utilize your IDE's debugger with breakpoints.
Example Test Structures
(Note: Examples are illustrative and may need adaptation)
Basic Component Test (Spectator)
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
import { MyComponent } from './my-component.component';
describe('MyComponent', () => {
let spectator: Spectator<MyComponent>;
const createComponent = createComponentFactory(MyComponent);
beforeEach(() => {
spectator = createComponent();
});
it('should create', () => {
// Assert
expect(spectator.component).toBeTruthy();
});
it('should display the title correctly', () => {
// Arrange
const testTitle = 'Test Title';
spectator.setInput('title', testTitle); // Assuming an @Input() title
spectator.detectChanges(); // Trigger change detection
// Act
const titleElement = spectator.query('h1');
// Assert
expect(titleElement).toHaveText(testTitle);
});
it('should emit output event on button click', () => {
// Arrange
const outputSpy = jest.fn();
spectator.output('actionClicked').subscribe(outputSpy); // Assuming an @Output() actionClicked
// Act
spectator.click('button.action-button');
// Assert
expect(outputSpy).toHaveBeenCalledTimes(1);
expect(outputSpy).toHaveBeenCalledWith(/* expected payload */);
});
});
Host Component Test (Spectator + Overrides)
import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest';
import { ParentComponent } from './parent.component'; // Assumed standalone, imports ChildComponent
import { ChildComponent } from './child.component'; // Assumed standalone
import { MockChildComponent } from './mock-child.component'; // Assumed standalone mock
describe('ParentComponent in Host with Overrides', () => {
let spectator: SpectatorHost<ParentComponent>;
// Override the standalone child component
const createHost = createHostFactory({
component: ParentComponent,
overrideComponents: [
[
ParentComponent,
{
remove: { imports: [ChildComponent] },
add: { imports: [MockChildComponent] },
},
],
],
template: `
<app-parent
[inputData]="hostInputData"
(outputEvent)="hostHandleOutput($event)">
</app-parent>`,
});
const mockOutputHandler = jest.fn();
beforeEach(() => {
spectator = createHost(undefined, {
hostProps: {
hostInputData: { id: 1, value: 'Test Data' },
hostHandleOutput: mockOutputHandler,
},
});
});
it('should render the parent component', () => {
// Assert
expect(spectator.component).toBeTruthy();
});
it('should render the mocked child component', () => {
// Arrange
const mockedChild = spectator.query(MockChildComponent); // Query the mocked child
// Assert
expect(mockedChild).toBeTruthy();
// You might check properties/methods on the mock instance if needed
// expect(mockedChild?.someMockProperty).toBe(...)
});
it('should handle output event from the component', () => {
// Arrange
const payload = { success: true };
// Assume ParentComponent emits outputEvent when something happens.
spectator.component.outputEvent.emit(payload);
// Assert
expect(mockOutputHandler).toHaveBeenCalledWith(payload);
});
});
Service Test (Spectator)
import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest';
import { DataService } from './data.service';
import { ApiService } from './api.service';
import { of } from 'rxjs';
describe('DataService', () => {
let spectator: SpectatorService<DataService>;
let apiServiceMock: ApiService;
const createService = createServiceFactory({
service: DataService,
mocks: [ApiService], // Mock the dependency
});
beforeEach(() => {
spectator = createService();
// Get the mocked instance provided by Spectator
apiServiceMock = spectator.inject(ApiService);
});
it('should be created', () => {
// Assert
expect(spectator.service).toBeTruthy();
});
it('should fetch data using ApiService', (done) => {
// Arrange
const mockData = [{ id: 1, name: 'Test Item' }];
// Configure the mock method
apiServiceMock.fetchItems.mockReturnValue(of(mockData));
// Act
spectator.service.getItems().subscribe(items => {
// Assert
expect(items).toEqual(mockData);
expect(apiServiceMock.fetchItems).toHaveBeenCalledTimes(1);
done(); // Signal async test completion
});
});
});
HTTP Service Test (Spectator)
import { createHttpFactory, HttpMethod, SpectatorHttp } from '@ngneat/spectator/jest';
import { UserHttpService } from './user-http.service';
import { AuthService } from './auth.service'; // Assume this provides a token
describe('UserHttpService', () => {
let spectator: SpectatorHttp<UserHttpService>;
let authServiceMock: AuthService;
const createHttp = createHttpFactory({
service: UserHttpService,
mocks: [AuthService], // Mock dependencies
});
beforeEach(() => {
spectator = createHttp();
authServiceMock = spectator.inject(AuthService);
// Setup mock return value for token
authServiceMock.getToken.mockReturnValue('fake-token-123');
});
it('should fetch users from the correct endpoint with GET', () => {
// Arrange
const mockUsers = [{ id: 1, name: 'User 1' }];
// Act
spectator.service.getUsers().subscribe(); // Call the method
// Assert
// Expect one request to the URL with the specified method
const req = spectator.expectOne('/api/users', HttpMethod.GET);
// Optionally check headers
expect(req.request.headers.get('Authorization')).toBe('Bearer fake-token-123');
// Respond to the request to complete the observable
req.flush(mockUsers);
});
it('should send user data to the correct endpoint with POST', () => {
// Arrange
const newUser = { name: 'New User', email: 'new@test.com' };
const createdUser = { id: 2, ...newUser };
// Act
spectator.service.createUser(newUser).subscribe();
// Assert
const req = spectator.expectOne('/api/users', HttpMethod.POST);
// Check request body
expect(req.request.body).toEqual(newUser);
// Check headers
expect(req.request.headers.get('Authorization')).toBe('Bearer fake-token-123');
// Respond
req.flush(createdUser);
});
});
Directive Test (Spectator)
import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/jest';
import { HighlightDirective } from './highlight.directive'; // Assumed standalone
// If HighlightDirective imported another standalone directive/component:
// import { SomeDependencyDirective } from './some-dependency.directive';
// import { MockSomeDependencyDirective } from './mock-some-dependency.directive';
describe('HighlightDirective', () => {
let spectator: SpectatorDirective<HighlightDirective>;
const createDirective = createDirectiveFactory({
directive: HighlightDirective,
// Example if the directive had standalone dependencies to override:
// overrideDirectives: [
// [
// HighlightDirective,
// {
// remove: { imports: [SomeDependencyDirective] },
// add: { imports: [MockSomeDependencyDirective] }
// }
// ]
// ],
template: `<div highlight="yellow" data-testid="highlighter">Test Content</div>`,
});
beforeEach(() => spectator = createDirective());
it('should apply the initial highlight color from input', () => {
// Assert
expect(spectator.element).toHaveStyle({ backgroundColor: 'yellow' });
});
it('should change style on mouseenter', () => {
// Act
spectator.dispatchMouseEvent(spectator.element, 'mouseenter');
spectator.detectChanges();
// Assert
expect(spectator.element).toHaveStyle({
backgroundColor: 'yellow', // Or expected mouseenter color
fontWeight: 'bold', // Example style change
});
});
it('should revert style on mouseleave', () => {
// Arrange
spectator.dispatchMouseEvent(spectator.element, 'mouseenter');
spectator.detectChanges();
// Act
spectator.dispatchMouseEvent(spectator.element, 'mouseleave');
spectator.detectChanges();
// Assert
expect(spectator.element).toHaveStyle({
backgroundColor: 'yellow', // Back to initial or default
fontWeight: 'normal', // Reverted style
});
});
});
Standalone Component Test (Spectator)
Testing standalone components is very similar to regular components. Spectator handles the necessary setup.
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
import { StandaloneButtonComponent } from './standalone-button.component';
// No need to import CommonModule etc. if they are in the component's imports array
describe('StandaloneButtonComponent', () => {
let spectator: Spectator<StandaloneButtonComponent>;
// Factory setup is the same
const createComponent = createComponentFactory({
component: StandaloneButtonComponent,
// No 'imports' needed here if the component imports them itself
// Mocks can still be provided if needed
// mocks: [SomeService]
});
beforeEach(() => {
spectator = createComponent();
});
it('should create', () => {
// Assert
expect(spectator.component).toBeTruthy();
});
it('should display label', () => {
// Arrange
spectator.setInput('label', 'Click Me');
spectator.detectChanges();
// Assert
expect(spectator.query('button')).toHaveText('Click Me');
});
});
Deferrable Views Test (Spectator)
Spectator provides helpers to control the state of @defer blocks.
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
import { DeferredComponent } from './deferred.component'; // Assume this uses @defer
describe('DeferredComponent', () => {
let spectator: Spectator<DeferredComponent>;
const createComponent = createComponentFactory({
component: DeferredComponent,
});
beforeEach(() => {
spectator = createComponent();
});
it('should initially show placeholder content', () => {
// Act: Render the placeholder state (often the default)
spectator.deferBlock().renderPlaceholder(); // Or renderIdle()
// Assert
expect(spectator.query('.placeholder-content')).toExist();
expect(spectator.query('.deferred-content')).not.toExist();
expect(spectator.query('.loading-indicator')).not.toExist();
});
it('should show loading state', () => {
// Act: Render the loading state
spectator.deferBlock().renderLoading();
// Assert
expect(spectator.query('.loading-indicator')).toExist();
expect(spectator.query('.placeholder-content')).not.toExist();
expect(spectator.query('.deferred-content')).not.toExist();
});
it('should render deferred content when completed', () => {
// Act: Render the completed state
spectator.deferBlock().renderComplete();
// Assert
expect(spectator.query('.deferred-content')).toExist();
expect(spectator.query('.placeholder-content')).not.toExist();
expect(spectator.query('.loading-indicator')).not.toExist();
});
it('should handle multiple defer blocks by index', () => {
// Act: Render the second defer block (index 1)
spectator.deferBlock(1).renderComplete();
// Assert
expect(spectator.query('.second-deferred-content')).toExist();
});
});
ng-mocks Example
Illustrates using MockBuilder and MockRender. Note: This example uses MockBuilder which might be less common with Spectator and standalone components, but MockInstance and MockProvider remain useful. Prefer Spectator's factories and overrideComponents where possible.
import { MockBuilder, MockInstance, MockRender, ngMocks, MockProvider } from 'ng-mocks';
import { ProfileComponent } from './profile.component'; // Assume standalone
import { AuthService } from './auth.service';
import { ReactiveFormsModule } from '@angular/forms'; // Needed if ProfileComponent imports it
describe('ProfileComponent (with ng-mocks helpers)', () => {
// Reset customizations after each test
MockInstance.scope();
// Configure TestBed using MockBuilder - less common with Spectator, but possible
beforeEach(() => {
// Keep ProfileComponent real, mock AuthService, keep ReactiveFormsModule if needed
return MockBuilder(ProfileComponent)
.keep(ReactiveFormsModule) // Keep if ProfileComponent imports it
.provide(MockProvider(AuthService)); // Use MockProvider for the service
});
it('should display user email from AuthService', () => {
// Arrange: Configure the mock instance *before* rendering
const mockEmail = 'test@example.com';
// Use MockInstance to configure the globally mocked AuthService
MockInstance(AuthService, 'getUserEmail', jest.fn().mockReturnValue(mockEmail));
// Act: Render the component using MockRender
const fixture = MockRender(ProfileComponent);
// Assert: Use ngMocks helpers or fixture directly
const emailDisplay = ngMocks.find(fixture, '.user-email');
expect(emailDisplay.nativeElement.textContent).toContain(mockEmail);
// Verify the mock was called (optional)
expect(MockInstance(AuthService).getUserEmail).toHaveBeenCalled();
});
it('should call AuthService.updateProfile on form submit', () => {
// Arrange
// Setup spy on the globally mocked AuthService instance
const updateSpy = MockInstance(AuthService, 'updateProfile', jest.fn());
const fixture = MockRender(ProfileComponent);
// Act: Simulate form input and submission
ngMocks.change(ngMocks.find(fixture, 'input[name="firstName"]'), 'NewName');
ngMocks.click(ngMocks.find(fixture, 'button[type="submit"]'));
// Assert
expect(updateSpy).toHaveBeenCalledWith(expect.objectContaining({ firstName: 'NewName' }));
});
});
End-to-End (E2E) Testing Attributes (data-what, data-which)
In end-to-end (E2E) testing of web applications, data-what and data-which are custom HTML data attributes used to enhance testability. They help identify elements or components in the DOM for testing purposes, making it easier for testing frameworks (like Selenium, Cypress, or WebdriverIO) to locate and interact with specific UI elements.
Purpose of data-what and data-which
- Identification: These attributes provide a stable, test-specific way to tag elements, avoiding reliance on brittle selectors like CSS classes, IDs, or XPath, which may change due to styling or structural updates.
- Semantics: They can describe the purpose (
data-what) or specific instance/context (data-which) of an element, improving clarity in test scripts. - Maintainability: By using custom attributes, developers can decouple test logic from presentation, reducing the risk of tests breaking when the UI is refactored.
Example Usage
<button data-what="submit-button" data-which="form-login">Login</button>
data-what="submit-button": Indicates the element's role or type (e.g., a submit button).data-which="form-login": Specifies the context or instance (e.g., the submit button for the login form).
In a test script (e.g., using WebdriverIO):
// Find the specific button
const loginButton = await $('[data-what="submit-button"][data-which="form-login"]');
await loginButton.click();
This targets the exact button, even if its class or ID changes.
Why Use Them?
- Robustness: Unlike classes or IDs, which are often tied to styling or JavaScript,
data-whatanddata-whichare dedicated to testing, reducing the chance of accidental changes breaking tests. - Clarity: They make test scripts self-documenting by describing the element's purpose and context.
- Scalability: In complex applications with multiple similar elements (e.g., multiple "Save" buttons),
data-whichhelps differentiate them.
Best Practices
-
Use clear, consistent naming conventions (e.g.,
data-what="button",data-which="checkout-page"). -
Avoid overusing or duplicating attributes unnecessarily.
-
Use additional
data-*attributes for dynamic identifiers: When you need to associate specific data like an item ID or name with an element for testing, add separatedata-*attributes instead of encoding them intodata-which. This keeps selectors clean and allows for more precise targeting.Example:
<oms-feature-return-summary-item [returnProcess]="item" data-what="list-item" data-which="return-process-item" [attr.data-item-id]="item.id" [attr.data-item-name]="item.name" ></oms-feature-return-summary-item>E2E Test Query (WebdriverIO):
// Find a specific item by ID const specificItem = await $('[data-what="list-item"][data-which="return-process-item"][data-item-id="12345"]'); await specificItem.waitForExist(); // Find all return process items const allItems = await $$('[data-what="list-item"][data-which="return-process-item"]'); console.log(`Found ${allItems.length} items.`); -
Ensure developers and testers agree on the attribute schema to maintain consistency.
-
Verify in Unit Tests: Ensure these
data-*attributes are correctly applied in your component unit tests. Use Spectator's querying capabilities to check for their presence and dynamic values.Unit Test Snippet (Spectator):
it('should apply correct data attributes', () => { // Arrange const testItem = { id: 'abc', name: 'Test Item' }; spectator.setInput('returnProcess', testItem); spectator.detectChanges(); // Act const element = spectator.queryHost('[data-what="list-item"]'); // Or query the specific element inside the component // Assert expect(element).toHaveAttribute('data-what', 'list-item'); expect(element).toHaveAttribute('data-which', 'return-process-item'); expect(element).toHaveAttribute('data-item-id', 'abc'); expect(element).toHaveAttribute('data-item-name', 'Test Item'); });
While data-what and data-which are not standardized HTML attributes, their use as custom data-* attributes aligns with HTML5 specifications, making them valid and widely supported. If you encounter them in a specific framework or codebase, check the project's testing documentation for their exact conventions.
Running Tests (Nx)
Use the Nx CLI to run tests efficiently within the monorepo.
Run Tests for a Specific Project
npx nx test <project-name>
# Example: npx nx test core-logging
# Example: npx nx test isa-app
Run Tests in Watch Mode
For active development, run tests continuously as files change:
npx nx test <project-name> --watch
Run Tests with Coverage Report
Generate a code coverage report:
npx nx test <project-name> --code-coverage
(Coverage reports are typically generated in the coverage/ directory)
Run a Specific Test File
Target a single test file:
npx nx test <project-name> --test-file=libs/core/utils/src/lib/my-util.spec.ts
Run Affected Tests
Optimize CI/CD by running tests only for projects affected by your code changes:
# Run tests for projects affected by changes compared to main branch
npx nx affected:test --base=main --head=HEAD
# Or run tests for uncommitted changes
npx nx affected:test
References
- Jest Documentation: Official Jest documentation.
- Spectator Documentation: Official Spectator documentation.
- ng-mocks Documentation: Official ng-mocks documentation.
- Angular Testing Guide: Official Angular testing concepts.