Spectator helps you get rid of all the boilerplate grunt work, leaving you with readable, sleek and streamlined unit tests.
Features
- ✅ Support for testing Angular components, directives and services - ✅ Easy DOM querying - ✅ Clean API for triggering keyboard/mouse/touch events - ✅ Testing ng-content - ✅ Custom Jasmine/Jest/Vitest Matchers (toHaveClass, toBeDisabled..) - ✅ Routing testing support - ✅ HTTP testing support - ✅ Built-in support for entry components - ✅ Built-in support for component providers - ✅ Auto-mocking providers - ✅ Strongly typed - ✅ Jest Support - ✅ Vitest Support
Sponsoring ngneat
Sponsorships aid in the continued development and maintenance of ngneat libraries. Consider asking your company to sponsor ngneat as its core to their business and application development.
$3
Elevate your support by becoming a Gold Sponsor and have your logo prominently featured on our README in the top 5 repositories.
$3
Boost your backing by becoming a Gold Sponsor and enjoy the spotlight with your logo prominently showcased in the top 3 repositories on our README.
$3
Become a bronze sponsor and get your logo on our README on GitHub.
Create a component factory by using the createComponentFactory() function, passing the component class that you want to test. The createComponentFactory() returns a function that will create a fresh component in each it block:
``ts import { Spectator, createComponentFactory } from '@ngneat/spectator'; import { ButtonComponent } from './button.component';
it('should have a success class by default', () => { expect(spectator.query('button')).toHaveClass('success'); });
it('should set the class name according to the [className] input', () => { spectator.setInput('className', 'danger'); expect(spectator.query('button')).toHaveClass('danger'); expect(spectator.query('button')).not.toHaveClass('success'); }); }); `
The createComponentFactory function can optionally take the following options which extends the basic Angular Testing Module options:
`ts const createComponent = createComponentFactory({ component: ButtonComponent, imports: [], providers: [], declarations: [], entryComponents: [], bindings: [], // Component bindings (inputBinding, outputBinding, twoWayBinding) forwarded to TestBed.createComponent componentProviders: [], // Override the component's providers componentViewProviders: [], // Override the component's view providers componentImports: [], // Override the component's imports in case of testing standalone component overrideModules: [], // Override modules overrideComponents: [], // Override components in case of testing standalone component overrideDirectives: [], // Override directives in case of testing standalone directive overridePipes: [], // Override pipes in case of testing standalone pipe mocks: [], // Providers that will automatically be mocked componentMocks: [], // Component providers that will automatically be mocked componentViewProvidersMocks: [], // Component view providers that will be automatically mocked detectChanges: false, // Defaults to true declareComponent: false, // Defaults to true disableAnimations: false, // Defaults to true shallow: true, // Defaults to false deferBlockBehavior: DeferBlockBehavior // Defaults to DeferBlockBehavior.Playthrough }); `
The createComponent() function optionally takes the following options: `ts it('should...', () => { spectator = createComponent({ // The component inputs props: { title: 'Click' }, // Override the component's providers // Note that you must declare it once in createComponentFactory providers: [], // Whether to run change detection (defaults to true) detectChanges: false });
The createComponentFactory() function supports Angular's component bindings using the bindings option. This allows you to bind signals and other reactive values to component inputs, outputs, and two-way bindings using Angular's inputBinding(), outputBinding(), and twoWayBinding() functions.
`ts import { signal, inputBinding, outputBinding, twoWayBinding } from '@angular/core';
describe('Component with Bindings', () => { it('should bind to component inputs', () => { const value = signal(1);
By providing overrideComponents options in scope of our createComponent() function we can define the way of overriding standalone component and it's dependencies `ts @Component({ selector: app-standalone-with-import, template:
Standalone component with import!
, imports: [StandaloneComponentWithDependency], standalone: true, }) export class StandaloneWithImportsComponent {}
expect(host.query('#standalone')).toContainText('Standalone component with import!'); expect(host.query('#standaloneWithDependency')).toContainText('Standalone component with override dependency!'); });
`
By passing
componentImports config to our createComponent() function we can remove and override imports directly in the tested standalone component. The componentImports property accepts an array of overrides. An override is an array of length 2, where the first entry is the original import to be removed from the tested component during the test and the second entry is the replacement import. In the example below, StandaloneComponentWithDependency is removed from the tested component, and MockStandaloneComponentWithDependency is added to the tested component imports. `ts @Component({ selector: app-standalone-with-import, template:
Standalone component with import!
, imports: [StandaloneComponentWithDependency], standalone: true, }) export class StandaloneWithImportsComponent {}
expect(host.query('#standalone')).toContainText('Standalone component with import!'); expect(host.query('#standaloneWithDependency')).toContainText('Standalone component with override dependency!'); });
`
The
createComponent() method returns an instance of Spectator which exposes the following API:
-
fixture - The tested component's fixture - component - The tested component's instance - element - The tested component's native element - debugElement - The tested fixture's debug element
-
flushEffects() - Provides a wrapper for TestBed.flushEffects() - runInInjectionContext() - Provides a wrapper for TestBed.runInInjectionContext() - inject() - Provides a wrapper for TestBed.inject(): `ts const service = spectator.inject(QueryService);
const fromComponentInjector = true; const service = spectator.inject(QueryService, fromComponentInjector);
` - detectChanges() - Runs detectChanges on the tested element/host: `ts spectator.detectChanges(); `
-
detectComponentChanges() - Runs detectChanges on the tested component ( not on the host ). You'll need this method in __rare__ cases when using a host and the tested component is onPush, and you want to force it to run a change detection cycle.`ts spectator.detectComponentChanges(); `
-
setInput() - Changes the value of an @Input() of the tested component. Method runs ngOnChanges with SimpleChanges manually if it exists. `ts it('should...', () => { spectator.setInput('className', 'danger');
spectator.setInput({ className: 'danger' }); });
` - output - Returns an Observable @Output() of the tested component: `ts it('should emit the $event on click', () => { let output; spectator.output('click').subscribe(result => (output = result));
` - tick(millis?: number) - Run the fakeAsync tick() function and call detectChanges(): `ts it('should work with tick', fakeAsync(() => { spectator = createComponent(ZippyComponent); spectator.component.update(); expect(spectator.component.updatedAsync).toBeFalsy(); spectator.tick(6000); expect(spectator.component.updatedAsync).not.toBeFalsy(); })) `
$3
Each one of the events can accept a SpectatorElement which can be one of the following:`ts type SpectatorElement = string | Element | DebugElement | ElementRef | Window | Document | DOMSelector; `
If not provided, the default element will be the host element of the component under test.
-
click() - Triggers a click event: `ts spectator.click(SpectatorElement); spectator.click(byText('Element')); ` - blur() - Triggers a blur event: `ts spectator.blur(SpectatorElement); spectator.blur(byText('Element')); ` Note that if using the jest framework, blur() only works if the element is focused. Details. - focus() - Triggers a focus event: `ts spectator.focus(SpectatorElement); spectator.focus(byText('Element')); ` - typeInElement() - Simulating the user typing: `ts spectator.typeInElement(value, SpectatorElement); spectator.typeInElement(value, byText('Element')); ` - dispatchMouseEvent() - Triggers a mouse event: `ts spectator.dispatchMouseEvent(SpectatorElement, 'mouseout'); spectator.dispatchMouseEvent(SpectatorElement, 'mouseout'), x, y, event); spectator.dispatchMouseEvent(byText('Element'), 'mouseout'); spectator.dispatchMouseEvent(byText('Element'), 'mouseout', x, y, event); ` - dispatchKeyboardEvent() - Triggers a keyboard event: `ts spectator.dispatchKeyboardEvent(SpectatorElement, 'keyup', 'Escape'); spectator.dispatchKeyboardEvent(SpectatorElement, 'keyup', { key: 'Escape', keyCode: 27 }) spectator.dispatchKeyboardEvent(byText('Element'), 'keyup', 'Escape'); spectator.dispatchKeyboardEvent(byText('Element'), 'keyup', { key: 'Escape', keyCode: 27 }) ` - dispatchTouchEvent() - Triggers a touch event: `ts spectator.dispatchTouchEvent(SpectatorElement, type, x, y); spectator.dispatchTouchEvent(byText('Element'), type, x, y); `
#### Custom Events
You can trigger custom events (@Output() of child components) using the following method:
In case you want to test events independently of any template (e.g. in presenter services) you can fallback on the underlying event creators. They are basically providing the same signature without the preceding element.
Note that each one of the above methods will also run
detectChanges().
$3
The Spectator API includes convenient methods for querying the DOM as part of a test: query, queryAll, queryLast , queryHost and queryHostAll. All query methods are polymorphic and allow you to query using any of the following techniques.
#### String Selector Pass a string selector (in the same style as you would when using jQuery or document.querySelector) to query for elements that match that path in the DOM. This method for querying is equivalent to Angular's By.css predicate. Note that native HTML elements will be returned. For example:
`ts // Returns a single HTMLElement spectator.query('div > ul.nav li:first-child'); // Returns an array of all matching HTMLElements spectator.queryAll('div > ul.nav li');
// Query from the document context spectator.query('div', { root: true });
` #### Type Selector Pass a type (such as a component, directive or provider class) to query for instances of that type in the DOM. This is equivalent to Angular's By.directive predicate. You can optionally pass in a second parameter to read a specific injection token from the matching elements' injectors. For example: `ts // Returns a single instance of MyComponent (if present) spectator.query(MyComponent);
// Returns the instance of
SomeService found in the instance of MyComponent that exists in the DOM (if present) spectator.query(MyComponent, { read: SomeService });
` #### DOM Selector Spectator allows you to query for elements using selectors inspired by dom-testing-library. The available selectors are:`ts spectator.query(byPlaceholder('Please enter your email address')); spectator.query(byValue('By value')); spectator.query(byTitle('By title')); spectator.query(byAltText('By alt text')); spectator.query(byLabel('By label')); spectator.query(byText('By text')); spectator.query(byText('By text', {selector: '#some .selector'})); spectator.query(byTextContent('By text content', {selector: '#some .selector'})); spectator.query(byRole('checkbox', { checked: true })); `
The difference between
byText and byTextContent is that the former doesn't match text inside a nested elements.
For example, in this following HTML
byText('foobar', {selector: 'div'}) won't match the following div, but byTextContent will: `html
foo bar
`
$3
Spectator allows you to query for nested elements within a parent element. This is useful when you have multiple instances of the same component on the page and you want to query for children within a specific one. The parent selector is a string selector or DOMSelector that is used to find the parent element. The parent selector is passed as the second parameter to the query methods. For example: `ts spectator.query(ChildComponent, { parentSelector: '#parent-component-1' }); spectator.queryAll(ChildComponent, { parentSelector: '#parent-component-1' });
#### Testing Select Elements Spectator allows you to test
elements easily, and supports multi select.
Example:
`ts it('should set the correct options on multi select', () => { const select = spectator.query('#test-multi-select') as HTMLSelectElement; spectator.selectOption(select, ['1', '2']); expect(select).toHaveSelectedOptions(['1', '2']); });
it('should set the correct option on standard select', () => { const select = spectator.query('#test-single-select') as HTMLSelectElement; spectator.selectOption(select, '1'); expect(select).toHaveSelectedOptions('1'); });
`
It also allows you to check if your
change event handler is acting correctly for each item selected. You can disable this if you need to pre set choices without dispatching the change event.
` You can also pass HTMLOptionElement(s) as arguments to selectOption and the toHaveSelectedOptions matcher. This is particularly useful when you are using [ngValue] binding on the