Giter Club home page Giter Club logo

spectator's Introduction

All Contributors spectator MIT commitizen PRs styled with prettier Build Status

A Powerful Tool to Simplify Your Angular Tests

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 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

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.

Gold Sponsors

Elevate your support by becoming a Gold Sponsor and have your logo prominently featured on our README in the top 5 repositories.

Silver Sponsors

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.

Bronze Sponsors

House of Angular

Become a bronze sponsor and get your logo on our README on GitHub.

Table of Contents

Installation

NPM

npm install @ngneat/spectator --save-dev

Yarn

yarn add @ngneat/spectator --dev

Testing Components

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:

import { Spectator, createComponentFactory } from '@ngneat/spectator';
import { ButtonComponent } from './button.component';

describe('ButtonComponent', () => {
  let spectator: Spectator<ButtonComponent>;
  const createComponent = createComponentFactory(ButtonComponent);

  beforeEach(() => spectator = createComponent());

  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:

const createComponent = createComponentFactory({
  component: ButtonComponent,
  imports: [],
  providers: [],
  declarations: [],
  entryComponents: [],
  componentProviders: [], // Override the component's providers
  componentViewProviders: [], // Override the component's view providers
  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:

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
  });

  expect(spectator.query('button')).toHaveText('Click');
});

By providing overrideComponents options in scope of our createComponent() function we can define the way of overriding standalone component and it's dependencies

@Component({
  selector: `app-standalone-with-import`,
  template: `<div id="standalone">Standalone component with import!</div>
  <app-standalone-with-dependency></app-standalone-with-dependency>`,
  imports: [StandaloneComponentWithDependency],
  standalone: true,
})
export class StandaloneWithImportsComponent {}

@Component({
  selector: `app-standalone-with-dependency`,
  template: `<div id="standaloneWithDependency">Standalone component with dependency!</div>`,
  standalone: true,
})
export class StandaloneComponentWithDependency {
  constructor(public query: QueryService) {}
}

@Component({
  selector: `app-standalone-with-dependency`,
  template: `<div id="standaloneWithDependency">Standalone component with override dependency!</div>`,
  standalone: true,
})
export class MockStandaloneComponentWithDependency {
  constructor() {}
}

it('should...', () => {
  const spectator = createHostFactory({
    component: StandaloneWithImportsComponent,
    template: `<div><app-standalone-with-import></app-standalone-with-import></div>`,
    overrideComponents: [
      [
        StandaloneWithImportsComponent,
        {
          remove: { imports: [StandaloneComponentWithDependency] },
          add: { imports: [MockStandaloneComponentWithDependency] },
        },
      ],
    ],
  });

  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()

  • inject() - Provides a wrapper for TestBed.inject():

const service = spectator.inject(QueryService);

const fromComponentInjector = true;
const service = spectator.inject(QueryService, fromComponentInjector);
  • detectChanges() - Runs detectChanges on the tested element/host:
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.
spectator.detectComponentChanges();
  • setInput() - Changes the value of an @Input() of the tested component. Method runs ngOnChanges with SimpleChanges manually if it exists.
it('should...', () => {
  spectator.setInput('className', 'danger');

  spectator.setInput({
    className: 'danger'
  });
});
  • output - Returns an Observable @Output() of the tested component:
it('should emit the $event on click', () => {
  let output;
  spectator.output('click').subscribe(result => (output = result));

  spectator.component.onClick({ type: 'click' });
  expect(output).toEqual({ type: 'click' });
});
  • tick(millis?: number) - Run the fakeAsync tick() function and call detectChanges():
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();
}))

Events API

Each one of the events can accept a SpectatorElement which can be one of the following:

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:
spectator.click(SpectatorElement);
spectator.click(byText('Element'));
  • blur() - Triggers a blur event:
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:
spectator.focus(SpectatorElement);
spectator.focus(byText('Element'));
  • typeInElement() - Simulating the user typing:
spectator.typeInElement(value, SpectatorElement);
spectator.typeInElement(value, byText('Element'));
  • dispatchMouseEvent() - Triggers a mouse event:
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:
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:
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:

spectator.triggerEventHandler(MyChildComponent, 'myCustomEvent', 'eventValue');
spectator.triggerEventHandler(MyChildComponent, 'myCustomEvent', 'eventValue', { root: true});

spectator.triggerEventHandler('app-child-component', 'myCustomEvent', 'eventValue');
spectator.triggerEventHandler('app-child-component', 'myCustomEvent', 'eventValue', { root: true});

Event Creators

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.

const keyboardEvent = createKeyboardEvent('keyup', 'ArrowDown'/*, targetElement */);
const mouseEvent = createMouseEvent('mouseout');
const touchEvent = createTouchEvent('touchmove');
const fakeEvent = createFakeEvent('input');

Keyboard helpers

spectator.keyboard.pressEnter();
spectator.keyboard.pressEscape();
spectator.keyboard.pressTab();
spectator.keyboard.pressBackspace();
spectator.keyboard.pressKey('a');
spectator.keyboard.pressKey('ctrl.a');
spectator.keyboard.pressKey('ctrl.shift.a');

Mouse helpers

spectator.mouse.contextmenu('.selector');
spectator.mouse.dblclick('.selector');

Note that each one of the above methods will also run detectChanges().

Queries

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:

// 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 });

spectator.query('app-child', { read: ChildServiceService });

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:

// 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 });

spectator.query(MyComponent, { read: ElementRef });
host.queryLast(ChildComponent);
host.queryAll(ChildComponent);

DOM Selector

Spectator allows you to query for elements using selectors inspired by dom-testing-library. The available selectors are:

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:

<div>
  <span>foo</span>
  <span>bar</span>
</div>

Parent Selector

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 that is used to find the parent element. The parent selector is passed as the second parameter to the query methods. For example:

spectator.query(ChildComponent, { parentSelector: '#parent-component-1' });
spectator.queryAll(ChildComponent, { parentSelector: '#parent-component-1' });

Testing Select Elements

Spectator allows you to test <select></select> elements easily, and supports multi select.

Example:

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.

API:

spectator.selectOption(selectElement: HTMLSelectElement, options: string | string[] | HTMLOptionElement | HTMLOptionElement[], config: { emitEvents: boolean } = { emitEvents: true });

Example:

it('should dispatch correct number of change events', () => {
  const onChangeSpy = spyOn(spectator.component, 'handleChange');
  const select = spectator.query('#test-onchange-select') as HTMLSelectElement;

  spectator.selectOption(select, ['1', '2'], { emitEvents: true});

  expect(select).toHaveSelectedOptions(['1', '2']);
  expect(onChangeSpy).toHaveBeenCalledTimes(2);
});

it('should not dispatch correct number of change events', () => {
  const onChangeSpy = spyOn(spectator.component, 'handleChange');
  const select = spectator.query('#test-onchange-select') as HTMLSelectElement;

  spectator.selectOption(select, ['1', '2'], { emitEvents: false});

  expect(select).toHaveSelectedOptions(['1', '2']);
  expect(onChangeSpy).not.toHaveBeenCalledTimes(2);
});

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 <option>:

it('should set the correct option on single select when passing the element', () => {
  const select = spectator.query('#test-single-select-element') as HTMLSelectElement;

  spectator.selectOption(select, spectator.query(byText('Two')) as HTMLOptionElement);

  expect(select).toHaveSelectedOptions(spectator.query(byText('Two')) as HTMLOptionElement);
});

Mocking Components

If you need to mock components, you can use the ng-mocks library. Instead of using CUSTOM_ELEMENTS_SCHEMA,which might hide some issues and won't help you to set inputs, outputs, etc., ng-mocks will auto mock the inputs, outputs, etc. for you.

Example:

import { createHostFactory } from '@ngneat/spectator';
import { MockComponent } from 'ng-mocks';
import { FooComponent } from './path/to/foo.component';

const createHost = createHostFactory({
  component: YourComponentToTest,
  declarations: [
    MockComponent(FooComponent)
  ]
});

Testing Single Component/Directive Angular Modules

Components (or Directives) that are declared in their own module can be tested by defining the component module in the imports list of the component factory together with the component. For example:

const createComponent = createComponentFactory({
  component: ButtonComponent,
  imports: [ButtonComponentModule],
});

When used like this, however, Spectator internally adds the component ButtonComponent to the declarations of the internally created new module. Hence, you will see the following error:

Type ButtonComponent is part of the declarations of 2 modules [...]

It is possible to tell Spectator not to add the component to the declarations of the internal module and, instead, use the explicitly defined module as is. Simply set the declareComponent property of the factory options to false:

const createComponent = createComponentFactory({
  component: ButtonComponent,
  imports: [ButtonComponentModule],
  declareComponent: false,
});

When using createDirectiveFactory set the declareDirective property of the factory options to false:

const createDirective = createDirectiveFactory({
  component: HighlightComponent,
  imports: [HighlightComponentModule],
  declareDirective: false,
});

Deferrable Views

The Spectator provides a convenient API to access the deferrable views (@defer {}).

Access the desired defer block using the spectator.deferBlock(optionalIndex) method. The optionalIndex parameter is optional and allows you to specify the index of the defer block you want to access.

  • Accessing the first defer block: Simply call spectator.deferBlock().
  • Accessing subsequent defer blocks: Use the corresponding index as an argument. For example, spectator.deferBlock(1) accesses the second block (zero-based indexing).

The spectator.deferBlock(optionalIndex) returns four methods for rendering different states of the specified defer block:

  • renderComplete() - Renders the complete state of the defer block.
  • renderPlaceholder() - Renders the placeholder state of the defer block.
  • renderLoading() - Renders the loading state of the defer block.
  • renderError() - Renders the error state of the defer block.

Example:

    @Component({
      selector: 'app-cmp',
      template: `
        @defer (on viewport) {
          <div>Complete state of the first defer block</div> <!--Parent Complete State-->
        } @placeholder {
          <div>Placeholder</div>
        }
      `,
    })
    class DummyComponent {}

    const createComponent = createComponentFactory({
      component: DummyComponent,
      deferBlockBehavior: DeferBlockBehavior.Manual,
    });

    it('should render the complete state', async () => {
      // Arrange
      const spectator = createComponent();

      // Act
      await spectator.deferBlock().renderComplete();

      // Assert
      expect(spectator.element.outerHTML).toContain('first defer block');
    });

Nested Deferrable Views

To access states within nested defer blocks, call the deferBlock method chaining from the returned block state method.

Example: Accessing the nested complete state:

// Assuming `spectator.deferBlock(0).renderComplete()` renders the complete state of the parent defer block
const parentCompleteState = await spectator.deferBlock().renderComplete();

// Access the nested complete state of the parent defer block
const nestedCompleteState = await parentCompleteState.renderComplete().deferBlock();

Complete Example:

    @Component({
      selector: 'app-cmp',
      template: `
        @defer (on viewport) {
          <div>Complete state of the first defer block</div> <!--Parent Complete State-->

          @defer {
            <div>Complete state of the nested defer block</div> <!--Nested Complete State-->
          }
        } @placeholder {
          <div>Placeholder</div>
        }
      `,
    })
    class DummyComponent {}

    const createComponent = createComponentFactory({
      component: DummyComponent,
      deferBlockBehavior: DeferBlockBehavior.Manual,
    });

    it('should render the first nested complete state', async () => {
      // Arrange
      const spectator = createComponent();

      // Act
      // Renders the parent complete state
      const parentCompleteState = await spectator.deferBlock().renderComplete();

      // Renders the nested complete state
      await parentCompleteState.deferBlock().renderComplete();

      // Assert
      expect(spectator.element.outerHTML).toContain('nested defer block');
    });

Testing with Host

Testing a component with a host component is a more elegant and powerful technique to test your component. It basically gives you the ability to write your tests in the same way that you write your code. Let's see it in action:

import { createHostFactory, SpectatorHost } from '@ngneat/spectator';

describe('ZippyComponent', () => {
  let spectator: SpectatorHost<ZippyComponent>;
  const createHost = createHostFactory(ZippyComponent);

  it('should display the title from host property', () => {
    spectator = createHost(`<zippy [title]="title"></zippy>`, {
      hostProps: {
        title: 'Spectator is Awesome'
      }
    });
    expect(spectator.query('.zippy__title')).toHaveText('Spectator is Awesome');
  });

  it('should display the "Close" word if open', () => {
    spectator = createHost(`<zippy title="Zippy title">Zippy content</zippy>`);

    spectator.click('.zippy__title');

    expect(spectator.query('.arrow')).toHaveText('Close');
    expect(spectator.query('.arrow')).not.toHaveText('Open');
  });
});

The host method returns an instance of SpectatorHost which extends Spectator with the following additional API:

  • hostFixture - The host's fixture
  • hostComponent - The host's component instance
  • hostElement - The host's native element
  • hostDebugElement - The host's fixture debug element
  • setHostInput - Changes the value of an @Input() of the host component
  • queryHost - Read more about querying in Spectator
  • queryHostAll - Read more about querying in Spectator

Setting inputs directly on a component using setInput or props is not possible when testing with a host component. Inputs should be set through hostProps or setHostInput instead, and passed through to your component in the template.

Custom Host Component

Sometimes it's helpful to pass your own host implementation. We can pass a custom host component to the createHostFactory() that will replace the default one:

@Component({ selector: 'custom-host', template: '' })
class CustomHostComponent {
  title = 'Custom HostComponent';
}

describe('With Custom Host Component', function () {
  let spectator: SpectatorHost<ZippyComponent, CustomHostComponent>;
  const createHost = createHostFactory({
    component: ZippyComponent,
    host: CustomHostComponent
  });

  it('should display the host component title', () => {
    spectator = createHost(`<zippy [title]="title"></zippy>`);
    expect(spectator.query('.zippy__title')).toHaveText('Custom HostComponent');
  });
});

Testing with Routing

For components which use routing, there is a special factory available that extends the default one, and provides a stubbed ActivatedRoute so that you can configure additional routing options.

describe('ProductDetailsComponent', () => {
  let spectator: SpectatorRouting<ProductDetailsComponent>;
  const createComponent = createRoutingFactory({
    component: ProductDetailsComponent,
    params: { productId: '3' },
    data: { title: 'Some title' }
  });

  beforeEach(() => spectator = createComponent());

  it('should display route data title', () => {
    expect(spectator.query('.title')).toHaveText('Some title');
  });

  it('should react to route changes', () => {
    spectator.setRouteParam('productId', '5');

     // your test here...
  });
});

Triggering a navigation

The SpectatorRouting API includes convenient methods for updating the current route:

interface SpectatorRouting<C> extends Spectator<C> {
  /**
   * Simulates a route navigation by updating the Params, QueryParams and Data observable streams.
   */
  triggerNavigation(options?: RouteOptions): void;

  /**
   * Updates the route params and triggers a route navigation.
   */
  setRouteParam(name: string, value: string): void;

  /**
   * Updates the route query params and triggers a route navigation.
   */
  setRouteQueryParam(name: string, value: string): void;

  /**
   * Updates the route data and triggers a route navigation.
   */
  setRouteData(name: string, value: any): void;

  /**
   * Updates the route fragment and triggers a route navigation.
   */
  setRouteFragment(fragment: string | null): void;

  /**
   * Updates the route url and triggers a route navigation.
   */
  setRouteUrl(url: UrlSegment[]): void;
}

Integration testing with RouterTestingModule

If you set the stubsEnabled option to false, you can pass a real routing configuration and setup an integration test using the RouterTestingModule from Angular.

Note that this requires promises to resolve. One way to deal with this, is by making your test async:

describe('Routing integration test', () => {
  const createComponent = createRoutingFactory({
    component: MyComponent,
    declarations: [OtherComponent],
    stubsEnabled: false,
    routes: [
      {
        path: '',
        component: MyComponent
      },
      {
        path: 'foo',
        component: OtherComponent
      }
    ]
  });

  it('should navigate away using router link', async () => {
    const spectator = createComponent();

    // wait for promises to resolve...
    await spectator.fixture.whenStable();

    // test the current route by asserting the location
    expect(spectator.inject(Location).path()).toBe('/');

    // click on a router link
    spectator.click('.link-1');

    // don't forget to wait for promises to resolve...
    await spectator.fixture.whenStable();

    // test the new route by asserting the location
    expect(spectator.inject(Location).path()).toBe('/foo');
  });
});

Routing Options

The createRoutesFactory function can take the following options, on top of the default Spectator options:

  • params: initial params to use in ActivatedRoute stub
  • queryParams: initial query params to use in ActivatedRoute stub
  • data: initial data to use in ActivatedRoute stub
  • fragment: initial fragment to use in ActivatedRoute stub
  • url: initial URL segments to use in ActivatedRoute stub
  • root: the value for root for the ActivatedRoute stub
  • parent: the value for parent for the ActivatedRoute stub
  • children: the value for children for the ActivatedRoute stub
  • firstChild: the value for firstChild for the ActivatedRoute stub
  • stubsEnabled (default: true): enables the ActivatedRoute stub, if set to false it uses RouterTestingModule instead
  • routes: if stubsEnabled is set to false, you can pass a Routes configuration for RouterTestingModule

Testing Directives

There is a special test factory for testing directives. Let's say we have the following directive:

@Directive({ selector: '[highlight]' })
export class HighlightDirective {

  @HostBinding('style.background-color') backgroundColor : string;

  @HostListener('mouseover')
  onHover() {
    this.backgroundColor = '#000000';
  }

  @HostListener('mouseout')
  onLeave() {
    this.backgroundColor = '#ffffff';
  }
}

Let's see how we can test directives easily with Spectator:

describe('HighlightDirective', () => {
  let spectator: SpectatorDirective<HighlightDirective>;
  const createDirective = createDirectiveFactory(HighlightDirective);

  beforeEach(() => {
    spectator = createDirective(`<div highlight>Testing Highlight Directive</div>`);
  });

  it('should change the background color', () => {
    spectator.dispatchMouseEvent(spectator.element, 'mouseover');

    expect(spectator.element).toHaveStyle({
      backgroundColor: 'rgba(0,0,0, 0.1)'
    });

    spectator.dispatchMouseEvent(spectator.element, 'mouseout');
    expect(spectator.element).toHaveStyle({
      backgroundColor: '#fff'
    });
  });

  it('should get the instance', () => {
    const instance = spectator.directive;
    expect(instance).toBeDefined();
  });
});

Setting inputs directly on a directive using setInput or props is not possible. Inputs should be set through hostProps or setHostInput instead, and passed through to your directive in the template.

Testing Services

The following example shows how to test a service with Spectator:

import { createServiceFactory, SpectatorService } from '@ngneat/spectator';

import { AuthService } from 'auth.service.ts';

describe('AuthService', () => {
  let spectator: SpectatorService<AuthService>;
  const createService = createServiceFactory(AuthService);

  beforeEach(() => spectator = createService());

  it('should not be logged in', () => {
    expect(spectator.service.isLoggedIn()).toBeFalsy();
  });
});

The createService() function returns SpectatorService with the following properties:

  • service - Get an instance of the service
  • inject() - A proxy for Angular TestBed.inject()

Additional Options

It's also possible to pass an object with options. For example, when testing a service you often want to mock its dependencies, as we focus on the service being tested.

For example:

@Injectable()
export class AuthService {
  constructor( private dateService: DateService )  {}

  isLoggedIn() {
    if( this.dateService.isExpired('timestamp') ) {
      return false;
    }
    return true;
  }
}

In this case we can mock the DateService dependency.

import { createServiceFactory, SpectatorService } from '@ngneat/spectator';

import { AuthService } from 'auth.service.ts';

describe('AuthService', () => {
  let spectator: SpectatorService<AuthService>;
  const createService = createServiceFactory({
    service: AuthService,
    providers: [],
    entryComponents: [],
    mocks: [DateService]
  });

  beforeEach(() => spectator = createService());

  it('should be logged in', () => {
    const dateService = spectator.inject(DateService);
    dateService.isExpired.and.returnValue(false);

    expect(spectator.service.isLoggedIn()).toBeTruthy();
  });
});

Testing Pipes

The following example shows how to test a pipe with Spectator:

import { SpectatorPipe, createPipeFactory } from '@ngneat/spectator';

import { StatsService } from './stats.service';
import { SumPipe } from './sum.pipe';

describe('SumPipe', () => {
  let spectator: SpectatorPipe<SumPipe>;
  const createPipe = createPipeFactory(SumPipe);

  it('should sum up the given list of numbers (template)', () => {
    spectator = createPipe(`{{ [1, 2, 3] | sum }}`);
    expect(spectator.element).toHaveText('6');
  });

  it('should sum up the given list of numbers (prop)', () => {
    spectator = createPipe(`{{ prop | sum }}`, {
      hostProps: {
        prop: [1, 2, 3]
      }
    });
    expect(spectator.element).toHaveText('6');
  });

  it('should delegate the summation to the service', () => {
    const sum = () => 42;
    const provider = { provide: StatsService, useValue: { sum } };
    spectator = createPipe(`{{ prop | sum }}`, {
      hostProps: {
        prop: [2, 40]
      },
      providers: [provider]
    });
    expect(spectator.element).toHaveText('42');
  });
});

The createPipe() function returns SpectatorPipe with the following properties:

  • hostComponent - Instance of the host component
  • debugElement - The debug element of the fixture around the host component
  • element - The native element of the host component
  • detectChanges() - A proxy for Angular TestBed.fixture.detectChanges()
  • inject() - A proxy for Angular TestBed.inject()

Setting inputs directly on a pipe using setInput or props is not possible. Inputs should be set through hostProps or setHostInput instead, and passed through to your pipe in the template.

Using Custom Host Component

The following example illustrates how to test a pipe using a custom host component:

import { Component, Input } from '@angular/core';
import { SpectatorPipe, createPipeFactory } from '@ngneat/spectator';

import { AveragePipe } from './average.pipe';
import { StatsService } from './stats.service';

@Component({
  template: `<div>{{ prop | avg }}</div>`
})
class CustomHostComponent {
  @Input() public prop: number[] = [1, 2, 3];
}

describe('AveragePipe', () => {
  let spectator: SpectatorPipe<AveragePipe>;
  const createPipe = createPipeFactory({
    pipe: AveragePipe,
    host: CustomHostComponent
  });

  it('should compute the average of a given list of numbers', () => {
    spectator = createPipe();
    expect(spectator.element).toHaveText('2');
  });

  it('should result to 0 when list of numbers is empty', () => {
    spectator = createPipe({
      hostProps: {
        prop: []
      }
    });
    expect(spectator.element).toHaveText('0');
  });

  it('should delegate the calculation to the service', () => {
    const avg = () => 42;
    const provider = { provide: StatsService, useValue: { avg } };
    spectator = createPipe({
      providers: [provider]
    });
    expect(spectator.element).toHaveText('42');
  });
});

Mocking Providers

For every Spectator factory, we can easily mock any provider.

Every service that we pass to the mocks property will be mocked using the mockProvider() function. The mockProvider() function converts each method into a Jasmine spy. (i.e jasmine.createSpy()).

Here are some of the methods it exposes:

dateService.isExpired.and.callThrough();
dateService.isExpired.and.callFake(() => fake);
dateService.isExpired.and.throwError('Error');
dateService.isExpired.andCallFake(() => fake);

However, if you use Jest as test framework and you want to utilize its mocking mechanism instead, import the mockProvider() from @ngneat/spectator/jest. This will automatically use the jest.fn() function to create a Jest compatible mock instead.

mockProvider() doesn't include properties. In case you need to have properties on your mock you can use 2nd argument:

const createService = createServiceFactory({
  service: AuthService,
  providers: [
    mockProvider(OtherService, {
      name: 'Martin',
      emitter: new Subject(),
      mockedMethod: () => 'mocked'
    })
  ],
});

Mocking OnInit dependencies

If a component relies on a service being mocked in the OnInit lifecycle method, change-detection needs to be disabled until after the services have been injected.

To configure this, change the createComponent method to have the detectChanges option set to false and then manually call detectChanges on the spectator after setting up the injected services.

const createComponent = createComponentFactory({
  component: WeatherDashboardComponent
});

it('should call the weather api on init', () => {
  const spectator = createComponent({
    detectChanges: false
  });
  const weatherService = spectator.inject(WeatherDataApi);
  weatherService.getWeatherData.andReturn(of(mockWeatherData));
  spectator.detectChanges();
  expect(weatherService.getWeatherData).toHaveBeenCalled();
});

Mocking constructor dependencies

If a component relies on a service being mocked in its constructor, you need to create and configure the mock, and to provide the mock when creating the component.

const createComponent = createComponentFactory({
  component: WeatherDashboardComponent
});

it('should call the weather api in the constructor', () => {
  const weatherService = createSpyObject(WeatherDataApi);
  weatherService.getWeatherData.andReturn(of(mockWeatherData));

  spectator = createComponent({
    providers: [
      { provide: WeatherDataApi, useValue: weatherService }
    ]
  });

  expect(weatherService.getWeatherData).toHaveBeenCalled();
});

Jest Support

By default, Spectator uses Jasmine for creating spies. If you are using Jest as test framework instead, you can let Spectator create Jest-compatible spies.

Just import one of the following functions from @ngneat/spectator/jest(instead of @ngneat/spectator), and it will use Jest instead of Jasmine. createComponentFactory(), createHostFactory(), createServiceFactory(), createHttpFactory(), mockProvider().

import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest';
import { AuthService } from './auth.service';
import { DateService } from './date.service';

describe('AuthService', () => {
  let spectator: SpectatorService<AuthService>;
  const createService = createServiceFactory({
    service: AuthService,
    mocks: [DateService]
  });

  beforeEach(() => spectator = createService());

  it('should not be logged in', () => {
    const dateService = spectator.inject<DateService>(DateService);
    dateService.isExpired.mockReturnValue(true);
    expect(spectator.service.isLoggedIn()).toBeFalsy();
  });

  it('should be logged in', () => {
    const dateService = spectator.inject<DateService>(DateService);
    dateService.isExpired.mockReturnValue(false);
    expect(spectator.service.isLoggedIn()).toBeTruthy();
  });
});

When using the component schematic you can specify the --jest flag to have the Jest imports used. In order to Jest imports the default, update angular.json:

"schematics": {
  "@ngneat/spectator:spectator-component": {
    "jest": true
  }
}

Testing with HTTP

Spectator makes testing data services, which use the Angular HTTP module, a lot easier. For example, let's say that you have service with three methods, one performs a GET, one a POST and one performs concurrent requests:

export class TodosDataService {
  constructor(private httpClient: HttpClient) {}

  getTodos() {
    return this.httpClient.get('api/todos');
  }

  postTodo(id: number) {
    return this.httpClient.post('api/todos', { id });
  }

  collectTodos() {
    return merge(
      this.httpClient.get('/api1/todos'),
      this.httpClient.get('/api2/todos')
    );
  }
}

The test for the above service should look like:

import { createHttpFactory, HttpMethod } from '@ngneat/spectator';
import { TodosDataService } from './todos-data.service';

describe('HttpClient testing', () => {
  let spectator: SpectatorHttp<TodosDataService>;
  const createHttp = createHttpFactory(TodosDataService);

  beforeEach(() => spectator = createHttp());

  it('can test HttpClient.get', () => {
    spectator.service.getTodos().subscribe();
    spectator.expectOne('api/todos', HttpMethod.GET);
  });

  it('can test HttpClient.post', () => {
    spectator.service.postTodo(1).subscribe();

    const req = spectator.expectOne('api/todos', HttpMethod.POST);
    expect(req.request.body['id']).toEqual(1);
  });

  it('can test current http requests', () => {
    spectator.service.getTodos().subscribe();
    const reqs = spectator.expectConcurrent([
        { url: '/api1/todos', method: HttpMethod.GET },
        { URL: '/api2/todos', method: HttpMethod.GET }
    ]);

    spectator.flushAll(reqs, [{}, {}, {}]);
  });
});

We need to create an HTTP factory by using the createHttpFactory() function, passing the service that you want to test. The createHttpFactory() returns a function which can be called to get an instance of SpectatorHttp with the following properties:

  • controller - A proxy for Angular HttpTestingController
  • httpClient - A proxy for Angular HttpClient
  • service - The service instance
  • inject() - A proxy for Angular TestBed.inject()
  • expectOne() - Expect that a single request was made which matches the given URL and it's method, and return its mock request

Global Injections

It's possible to define injections which will be available for each test without the need to re-declare them in each test:

// test.ts
import { defineGlobalsInjections } from '@ngneat/spectator';
import { TranslocoModule } from '@ngneat/transloco';

defineGlobalsInjections({
  imports: [TranslocoModule],
});

Please be aware, that defineGlobalsInjections() must be called before the modules are loaded. In the default Angular test.ts this means before this line:

context.keys().map(context);

Component Providers

By default, the original component providers (e.g. the providers on the @Component) are not touched.

However, in most cases, you want to access the component's providers in your test or replace them with mocks.

For example:

@Component({
  template: '...',
  providers: [FooService]
})
class FooComponent {
  constructor(private fooService: FooService} {}

  // ...
}

Use the componentProviders to replace the FooService provider:

const createComponent = createComponentFactory({
  component: FooComponent,
  componentProviders: [
    {
      provide: FooService,
      useValue: someThingElse
    }
  ]
})

Or mock the service by using componentMocks:

const createComponent = createComponentFactory({
  component: FooComponent,
  componentMocks: [FooService]
});

To access the provider, get it from the component injector using the fromComponentInjector parameter:

spectator.inject(FooService, true)

In the same way you can also override the component view providers by using the componentViewProviders and componentViewProvidersMocks.

The same rules also apply to directives using the directiveProviders and directiveMocks parameters.

Custom Matchers

expect('.zippy__content').not.toExist();
expect('.zippy__content').toHaveLength(3);
expect('.zippy__content').toHaveId('id');
expect(spectator.query('.zippy')).toHaveAttribute('id', 'zippy');
expect(spectator.query('.zippy')).toHaveAttribute({id: 'zippy'});
expect(spectator.query('.checkbox')).toHaveProperty('checked', true);
expect(spectator.query('.img')).toHaveProperty({src: 'assets/myimg.jpg'});
expect(spectator.query('.img')).toContainProperty({src: 'myimg.jpg'});

// Note that toHaveClass accepts classes only in strict order. If order is irrelevant, disable strict mode manually.
expect('.zippy__content').toHaveClass('class');
expect('.zippy__content').toHaveClass('class-a, class-b');
expect('.zippy__content').not.toHaveClass('class-b, class-a');
expect('.zippy__content').toHaveClass(['class-a', 'class-b']);
expect('.zippy__content').not.toHaveClass(['class-b', 'class-a']);

expect('.zippy__content').toHaveClass('class', { strict: false });
expect('.zippy__content').toHaveClass('class-a, class-b', { strict: false });
expect('.zippy__content').toHaveClass('class-b, class-a', { strict: false });
expect('.zippy__content').toHaveClass(['class-b', 'class-a'], { strict: false });
expect('.zippy__content').toHaveClass(['class-b', 'class-a'], { strict: false });

// Note that toHaveText only looks for the existence of a string, not if the string is exactly the same. If you want to verify that the string is completely the same, use toHaveExactText.
// Note that if you want to verify that the string is completely the same, but trimmed first, use toHaveExactTrimmedText.
// Note that if you pass multiple values, Spectator checks the text of each array element against the index of the element found.
expect('.zippy__content').toHaveText('Content');
expect('.zippy__content').toHaveText(['Content A', 'Content B']);
expect('.zippy__content').toHaveText((text) => text.includes('..'));
expect('.zippy__content').toContainText('Content');
expect('.zippy__content').toContainText(['Content A', 'Content B']);
expect('.zippy__content').toHaveExactText('Content');
expect('.zippy__content').toHaveExactText(['Content A', 'Content B']);
expect('.zippy__content').toHaveExactTrimmedText('Content');
expect('.zippy__content').toHaveExactTrimmedText(['Content A', 'Content B']);
expect('.zippy__content').toHaveValue('value');
expect('.zippy__content').toContainValue('value');

// Note this looks for multiple elements with the class and checks the value of each array element against the index of the element found
expect('.zippy__content').toHaveValue(['value a', 'value b']);
expect('.zippy__content').toContainValue(['value a', 'value b']);
expect(spectator.element).toHaveStyle({backgroundColor: 'rgba(0, 0, 0, 0.1)'});
expect('.zippy__content').toHaveData({data: 'role', val: 'admin'});
expect('.checkbox').toBeChecked();
expect('.checkbox').toBeIndeterminate();
expect('.button').toBeDisabled();
expect('div').toBeEmpty();
expect('div').toBeHidden();
expect('element').toBeSelected();
// Notice that due to restrictions within Jest (not applying actual layout logic in virtual DOM), certain matchers may result in false positives. For example width and height set to 0
expect('element').toBeVisible();
expect('input').toBeFocused();
expect('div').toBeMatchedBy('.js-something');
expect(spectator.component.object).toBePartial({ aProperty: 'aValue' });
expect('div').toHaveDescendant('.child');
expect('div').toHaveDescendantWithText({selector: '.child', text: 'text'});

Schematics

Generate component, service, and directive with Spectator spec templates with Angular Cli: (when using it as default)

Component

  • Default spec: ng g cs dashrized-name
  • Spec with a host: ng g cs dashrized-name --withHost=true
  • Spec with a custom host: ng g cs dashrized-name --withCustomHost=true

Service:

  • Default spec: ng g ss dashrized-name
  • Spec for testing http data service: ng g ss dashrized-name --isDataService=true

Directive:

ng g ds dashrized-name

Default Schematics Collection

To use spectator as the default collection in your Angular CLI project, add it to your angular.json:

ng config cli.defaultCollection @ngneat/spectator

The spectator schematics extend the default @schematics/angular collection. If you want to set defaults for schematics such as generating components with scss file, you must change the schematics package name from @schematics/angular to @ngneat/spectator in angular.json:

"schematics": {
  "@ngneat/spectator:spectator-component": {
    "style": "scss"
  }
}

Working Spectator & Jest Sample Repo and Karma Comparison

The examples in Karma from Angular docs testing developer guide has been reproduced in Spectator and Jest. (For convenience, this is the local version of the Karma examples.)

The Spectator & Jest version can be accessed here.

Core Team

Netanel Basal
Netanel Basal
Dirk Luijk
Dirk Luijk
Ben Elliott
Ben Elliott

Contributors

Thanks goes to these wonderful people (emoji key):~~~~


I. Sinai

📖 👀 🎨

Valentin Buryakov

💻 🤔

Ben Grynhaus

🐛 💻

Martin Nuc

💻

Lars Gyrup Brink Nielsen

📦 ⚠️

Andrew Grekov

💻 🔧

Jeroen Zwartepoorte

💻

Oliver Schlegel

💻

Rex Ye

🔧 💻

tchmura

💻

Yoeri Nijs

💻

Anders Skarby

💻

Gregor Woiwode

💻

Alexander Sheremetev

🐛 💻

Mike

💻

Mehmet Erim

📖

Brett Eckert

💻

Ismail Faizi

💻

Maxime

📖

Jonathan Bonnefoy

💻

Colum Ferry

💻

Chris Cooper

💻

Marc Scheib

📖

dgsmith2

💻

dedwardstech

💻 🤔

tamasfoldi

💻 🤔

Paolo Caleffi

💻

Toni Villena

💻

Itay Oded

💻

Guillaume de Jabrun

💻

Anand Tiwary

💻

Ales Doganoc

💻

Zoltan

💻

Vitalii Baziuk

💻

clementlemarc-certua

💻

Yuriy Grunin

💻

Andrey Chalkin

💻

Steven Harris

💻 📖

Richard Sahrakorpi

💻

Dominik Kremer

💻

Mehmet Ozan Turhan

💻

Vlad Lashko

💻

William Tjondrosuharto

💻

Chaz Gatian

💻

Pavel Korobov

💻

Enno Lohmann

💻

Pawel Boguslawski

💻

Tobias Wittwer

💻 📖

Mateo Tibaquirá

💻

This project follows the all-contributors specification. Contributions of any kind welcome!

spectator's People

Contributors

anandtiwary avatar aniederer-chatham avatar arturovt avatar benelliott avatar bogusweb avatar chimurai avatar coffee-tea avatar dedwardstech avatar dirkluijk avatar jmbeach avatar johncrim avatar jpzwarte avatar kanafghan avatar kfrancois avatar l2jliga avatar layzeedk avatar martinnuc avatar matheo avatar mehmet-erim avatar netanel-utila avatar netanelbasal avatar profanis avatar rexebin avatar steven-harris avatar stones avatar th0r avatar thekiba avatar va-stefanek avatar williamjuan027 avatar yoerinijs avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

spectator's Issues

angular 6?

Any plans to support angular 6 soon (without needing rxjs-compat)?

Dedicated page in docs about life cycle hooks

Hi,

Some of my co-workers had trouble with life cycle hooks. This may be obvious for experienced Angular devs, but maybe it would be nice to have a dedicated page in the docs Components -> Lifecycle hooks:

Lifecycle hooks

When using regular Spectator (with createTestComponentFactory), lifecycle hooks on the tested component are not triggered. You have trigger them yourself, e.g. by calling spectator.component.ngOnChanges(). This is because live cycle hooks only get triggered when input bindings are updated by a parent view.

To profit from automatic life cycle hooks, you will need the SpectatorWithHost.

host = createHost(`<hello title="Hello"></hello>`);

Please note that, for complex input bindings, createHost('<my-component></my-component>', true, { someInput: 'value' }) will not work, because the third argument will only update the component instance and not trigger life cycle hooks. Instead, use a custom host:

@Component({selector: 'host'})
class HostComponent {
  myTitle = 'World';
}

describe('HelloComponent', () => {
  let host: SpectatorWithHost<HelloComponent, HostComponent>;

  const createHost = createHostComponentFactory({
    component: HelloComponent,
    host: HostComponent
  });

  it('does trigger lifecycle hooks when using a host ', () => {
    host = createHost(`<hello [title]="myTitle"></hello>`);
  });
});

I hope this can be of any value for the docs ;)

TS strict mode: implicitly has an 'any' type

We use typescript strict mode in our application but spectator fails to compile with following error:

ERROR in node_modules/@netbasal/spectator/lib/matchers-types.d.ts(13,18): error TS7031: Binding element 'attr' implicitly has an 'any' type.
node_modules/@netbasal/spectator/lib/matchers-types.d.ts(13,24): error TS7031: Binding element 'val' implicitly has an 'any' type.
node_modules/@netbasal/spectator/lib/matchers-types.d.ts(14,21): error TS7006: Parameter 'attr' implicitly has an 'any' type.
node_modules/@netbasal/spectator/lib/matchers-types.d.ts(14,27): error TS7006: Parameter 'val' implicitly has an 'any' type.
node_modules/@netbasal/spectator/lib/matchers-types.d.ts(18,18): error TS7031: Binding element 'prop' implicitly has an 'any' type.
node_modules/@netbasal/spectator/lib/matchers-types.d.ts(18,24): error TS7031: Binding element 'val' implicitly has an 'any' type.
node_modules/@netbasal/spectator/lib/matchers-types.d.ts(19,20): error TS7006: Parameter 'prop' implicitly has an 'any' type.
node_modules/@netbasal/spectator/lib/matchers-types.d.ts(19,26): error TS7006: Parameter 'val' implicitly has an 'any' type.
node_modules/@netbasal/spectator/lib/matchers-types.d.ts(21,20): error TS7006: Parameter 'prop' implicitly has an 'any' type.
node_modules/@netbasal/spectator/lib/matchers-types.d.ts(21,26): error TS7006: Parameter 'val' implicitly has an 'any' type.
node_modules/@netbasal/spectator/lib/matchers-types.d.ts(29,18): error TS7031: Binding element 'data' implicitly has an 'any' type.
node_modules/@netbasal/spectator/lib/matchers-types.d.ts(29,24): error TS7031: Binding element 'val' implicitly has an 'any' type.
node_modules/@netbasal/spectator/lib/matchers-types.d.ts(49,32): error TS7031: Binding element 'selector' implicitly has an 'any' type.
node_modules/@netbasal/spectator/lib/matchers-types.d.ts(49,42): error TS7031: Binding element 'text' implicitly has an 'any' type.

I created a repository to reproduce the issue: https://github.com/MartinNuc/spectator-strict

just run npm install and npm test.

I was able to fix the issue by adding types to matchers-types.d.ts. I will create PR.

v1.13.1 not released

Hi, unfortunately v1.13.1 isn't available on npm: https://www.npmjs.com/package/@netbasal/spectator

I will be needing the patch which supports content projection in mocked components. It exists in the aforementioned release.

As we are still using angular 5, I cannot use the next available version which contains this patch (v2.0.2). Will it be possible to republish v1.13.1? Thanks! We are currently using v1.13.0.

For the meantime, our workaround is to explicitly set the template option in MockComponent to <ng-content></ng-content>

Support host component from another module

Error I'm getting:

	Error: Type EditorTestHostComponent is part of the declarations of 2 modules: AtlasDevToolsModule and DynamicTestModule! Please consider moving EditorTestHostComponent to a higher module that imports AtlasDevToolsModule and DynamicTestModule. You can also create a new NgModule that exports and includes EditorTestHostComponent then import that NgModule in AtlasDevToolsModule and DynamicTestModule.

Is it possible to support my host component coming from another module?

Missing types for query() method

According to internals.d.ts, the query method has the following types:

    /**
     *
     * @param {Type<T> | string} directiveOrSelector
     * @param {{read}} options
     * @returns {T}
     */
    query<R>(directiveOrSelector: string, options?: {
        read;
    }): Element;
    query<R>(directiveOrSelector: Type<any>, options?: {
        read;
    }): R;

That means that you have to cast the return value of the query method when passing a selector before you can use it. Can't we change the API so you can do: spectator.query<HTMLElement>('a').click()?

SpectatorWithHost resetting inputs when detectChanges is set to true

Given the following component:

@Component({
	selector: 'my-component',
    template: `
      <div>
        <ng-container [ngTemplateOutlet]="content"></ng-content>
      </div>`,
	changeDetection: ChangeDetectionStrategy.OnPush,
	encapsulation: ViewEncapsulation.None,
})
export class MyComponent {
  @Input() someInput: boolean;
  @Input() content: TemplateRef;
}

And the following test code:

let spectator: SpectatorWithHost<MyComponent>;
const createHost = createHostComponentFactory({
  component: MyComponent,
  shallow: true
});

it('input test', () => {
  spectator = createHost(`
    <ng-template #content>
      <span>Sample content</span>
    </ng-template>

    <my-component
        [someInput]="someInput"
        [content]="content">
    </my-component>
`, true, { someInput: true })

  expect(spectator.component.someInput).toBe(true);
});

The test 'input test' fails, and someInput is undefined.

passing false as the detectChanges parameter (to createHost) results in someInput being true, but as soon as you run a changeDetection (via spectator.detectChanges() to check the DOM, the values reset.

Error when running unit test

Component:

import {Component, Input} from "@angular/core";

@Component({
  selector: "app-test-component",
  template: `<div>Test text</div>`
})
export class TestComponent{
  constructor() {
  }
}

Unit test:

import {createHost, EasyTestWithHost} from 'ngx-easy-test';
import {TestComponent} from './test.component';

describe('TestComponent', () => {
  type Context = EasyTestWithHost<TestComponent>;

  createHost(TestComponent);

  it('should create', function (this: Context) {
    this.create(`<app-test-component></app-test-component>`);
    expect(this.testedComponent.toBeTruthy()))
  });
});

Console output when running npm run test:

ERROR in C:/Users.../node_modules/ngx-easy-test/src/easy-test.d.ts (2,13): All declarations of 'Matchers' must have identical type parameters.
ERROR in c:/users/.../node_modules/@types/jasmine/index.d.ts (26,41): Generic type 'Matchers<T>' requires 1 type argument(s).
ERROR in c:/users/.../node_modules/@types/jasmine/index.d.ts (27,39): Generic type 'Matchers<T>' requires 1 type argument(s).
ERROR in c:/users/.../node_modules/@types/jasmine/index.d.ts (152,24): Generic type 'Matchers<T>' requires 1 type argument(s).
ERROR in c:/users/.../node_modules/@types/jasmine/index.d.ts (302,15): All declarations of 'Matchers' must have identical type parameters.
ERROR in c:/users/.../node_modules/@types/jasmine/index.d.ts (333,14): Generic type 'Matchers<T>' requires 1 type argument(s).
ERROR in c:/users/.../node_modules/@types/jasmine/index.d.ts (429,24): Generic type 'Matchers<T>' requires 1 type argument(s).
ERROR in c:/users/.../node_modules/@types/jasmine/index.d.ts (442,30): Generic type 'Matchers<T>' requires 1 type argument(s).

Windows 10
Node 8.0
Typescript 2.3.4
Angular: 4.3.1

host.query doesnt work for directives

When testing a directive according to https://netbasal.gitbook.io/spectator/directives the host.query doesn't work because it expects to have debugElement which is not defined for a directive.

Results in TypeError: Cannot read property 'query' of null with stack trace:

    at <Jasmine>
    at http://localhost:9877/node_modules/@netbasal/spectator/fesm5/netbasal-spectator.js?:896:1
    at SpectatorWithHost.push../node_modules/@netbasal/spectator/fesm5/netbasal-spectator.js.Spectator.query (http://localhost:9877/node_modules/@netbasal/spectator/fesm5/netbasal-spectator.js?:517:1)

Possible solution: use hostDebugElement when debugElement is not defined.

What do you think?

Angular 7 support

Installing the latest version of spectator (2.0.3) in a project with Angular 7.0.0 gives the following warnings:

warning " > @netbasal/[email protected]" has incorrect peer dependency "@angular/common@^6.0.0".
warning " > @netbasal/[email protected]" has incorrect peer dependency "@angular/core@^6.0.0".

Since Angular 7 was officially released on 2018-10-18, it would be nice if spectator supported it.

Imports in createService method doesn't work

This is an example tested service, which uses Angular's FormBuilder:

import {Injectable} from '@angular/core';
import {FormBuilder} from '@angular/forms';

@Injectable()
export class FormsService {
  constructor(
    private fb: FormBuilder,
  ) {}
}

Here's the test case for it, using the 'imports' property

import {ReactiveFormsModule} from "@angular/forms";
import {createService} from "@netbasal/spectator";
import {FormsService} from "./media.service";

describe('FormsService', () => {
  const spectator = createService<FormsService>({
    imports: [
      ReactiveFormsModule
    ],
    service: FormsService,
  });

  it('should create', () => {
    expect(spectator.service).toBeTruthy();
  });
});

What I get in an error - it doesn't find the FormBuilder:
Error: StaticInjectorError(DynamicTestModule)[FormsService -> FormBuilder]: StaticInjectorError(Platform: core)[FormsService -> FormBuilder]: NullInjectorError: No provider for FormBuilder!

Error with @angular/animations

On work, we have a Module with some Services to help the teams with some stuff related to our architecture (like oauth and so on...). When I use createService, it gives me the this error:

ERROR in ./~/@angular/platform-browser/bundles/platform-browser-animations.umd.js
Module not found: Error: Cannot resolve module '@angular/animations' in C:\Users\davi.carvalho\Documents\ANGL-SPAWEBBGRL\node_modules\@angular\platform-browser\bundles
 @ ./~/@angular/platform-browser/bundles/platform-browser-animations.umd.js 7:145-175

ERROR in ./~/@angular/platform-browser/bundles/platform-browser-animations.umd.js
Module not found: Error: Cannot resolve module '@angular/animations/browser' in C:\Users\davi.carvalho\Documents\ANGL-SPAWEBBGRL\node_modules\@angular\platform-browser\bundles
 @ ./~/@angular/platform-browser/bundles/platform-browser-animations.umd.js 7:177-215

ERROR in [at-loader] src\guards-module\guards\authMBS.guard.spaa.spec.ts:85:25
    Argument of type '"hasValidDataAccess"' is not assignable to parameter of type '"RouteForbidden" | "UrlUserMenu" | "getRolesList" | "canActivate"'.

ERROR in [at-loader] src\guards-module\guards\authMBS.guard.spaa.spec.ts:104:25
    Argument of type '"hasValidDataAccess"' is not assignable to parameter of type '"RouteForbidden" | "UrlUserMenu" | "getRolesList" | "canActivate"'.
webpack: Failed to compile.
19 07 2018 11:02:31.900:INFO [karma]: Karma v1.2.0 server started at http://localhost:8090/
19 07 2018 11:02:31.908:INFO [launcher]: Launching browser PhantomJS with unlimited concurrency
19 07 2018 11:02:32.443:INFO [launcher]: Starting browser PhantomJS
19 07 2018 11:02:53.710:INFO [PhantomJS 2.1.1 (Windows 8 0.0.0)]: Connected on socket /#nn-3uL5yD-e4sA_6AAAA with id 40538519
PhantomJS 2.1.1 (Windows 8 0.0.0) ERROR
  SyntaxError: Unexpected token 'const'
  at karma-test-shim.js:78894

PhantomJS 2.1.1 (Windows 8 0.0.0) ERROR
  SyntaxError: Unexpected token 'const'
  at karma-test-shim.js:78894


=============================== Coverage summary ===============================
Statements   : 100% ( 0/0 )
Branches     : 100% ( 0/0 )
Functions    : 100% ( 0/0 )
Lines        : 100% ( 0/0 )
================================================================================

So it breaks the tests and say that I don't have @angular/animations, since it's just services, we don't have @angular/animations in it.

I'm using version 1.2.1, because we are using Angular 5

Suggestion: toBeEmpty() should ignore comment nodes

I'm using toBeEmpty() to ensure that an element with a sub-element that has either an *ngFor or an *ngIf contains no children, but both of those directives add a comment node to the DOM, which causes toBeEmpty() to return false.

If you don't like this idea, maybe adding another matcher that has this behavior is a better alternative.

Library node_modules is checked into git

I am working on a new feature that I would like to eventually submit as a pull request, but that feature makes some changes to the library's dependencies and I was surprised to find that the dependencies are checked into git. Is this necessary? For one, it makes it hard to see the real changes a pull request is making as they are buried in the noise of minor dependency updates.

Testing Components with service in OnInit

Hi,

While is was testing with your beautiful library I ran into a exception at the mocks part.
In most of my components I subscribe to an observable, for example this.route.paramMap.subscribe(paramMap => {}); and it will crash with TypeError: Cannot read property 'subscribe' of null.

So I have created a Gist with minimal setup code as example for other people.

Maybe it's nice to make a way to configure the return value of the mocked functions. I saw the default return value was 'null' but with observables it would be nice to have some way to configure it.

Example (brain dump):

@Component({})
class MyComponent {
    constructor(private barService: BarService,
                private route: ActivatedRoute) {}

    ngOnInit(): void {
        // this fails without configuring the mocks:

        // ERROR: Cannot read property 'subscribe' of null
        this.route.paramMap.pipe(
                switchMap(() => this.barService.getFoo())
            ).subscribe(); 

        // and this fails too...
        this.barService.getBar();
    }
}

describe('', () => {
    // proposal, a way to configure mocks in the factory?
    // or just mock all Observables on the mock prototypes by default...

    const createComponent = createTestComponentFactory({
        component: MyComponent,
        mocks: [[BarService, { getFoo: of('lorem'), getBar: 'ipsum' }]]
    });


    it('', () => {
        const spectator = createComponent();

        // detectChanges should work without issues
    });
})

Not able to test if a Function has been called from constructor.

This is my code :
//app.component.ts

    spectator = createComponent({}, false);
    app = spectator.debugElement.componentInstance;
    translate = TestBed.get(TranslateService);
  });

  it('should create the app', async(() => {
    spyOn(spectator.component, 'setupLanguage').and.callThrough();
    spectator.fixture.detectChanges();
    app = spectator.debugElement.componentInstance;
    expect(app).toBeTruthy();
    expect(spectator.component.setupLanguage).toHaveBeenCalled();
  }));```

**//app.component.spec.ts**
```export class AppComponent {
  title = 'app';
  languageKeys = ['en', 'de', 'fr', 'it'];

  constructor(private translate: TranslateService) {
    this.setupLanguage();
  }

//Here is the test output
screen shot 2018-05-16 at 12 47 29 pm

Document mocking ngrx store

You should document how to mock ngrx store in a component/service.

Here is an example of how to mock the store in a service where the service needs data from selectors for a small slice of the store state. I'd love to know if there is a way to simplify this even more...

import { SearchService } from './search.service';
import { createService } from '@netbasal/spectator';
import { of } from 'rxjs';
import { StoreModule, Action } from '@ngrx/store';
import * as fromBook from '@book/state';

function reducer(state = {}, action: Action): {} {
  switch (action.type) {
    default:
      return state;
  }
}

describe('SearchService', () => {
  const spectator = createService({
    service: SearchService,
    imports: [
      StoreModule.forRoot(reducer),
      StoreModule.forFeature('book', fromBook.reducer)
    ]
  });

  beforeEach(() => {
    // const store = spectator.get(Store) just returns {} ???
    // Grab the private instance of the store in the service under test:
    const store = spectator.service['store'];
    store.dispatch(new fromBook.SetCurrentBookId(42));
    store.dispatch(new fromBook.SetCurrentProductId(1));
  });

  it('can search', () => {
    const testData = {};
    const spy = spyOn(myObject, 'someMethod').and.returnValue(of(testData));
    spectator.service.search('something');
    expect(spy).toHaveBeenCalledWith(42, 1, true, true);
  });

});

Testing Output() in a structural directive

Problem
It's been impossible for a long time (possibly still is) to use Output() on a structural directive in Angular. To do that, one must use a de-sugarized version of a structural directive, i.e. with ng-template. I've been trying to test that case with spectator, since that's what I'm using to test my app, but I can't write the tests correctly with custom host component. I looked into that issue #13 but it didn't help me.

Example code
https://stackblitz.com/github/slawojstanislawski/spectator-structural

All instances of a MockComponent share the same event emitter, therefore I cannot emit events on individual instances.

Hi!

First of, thanks for an awesome testing framework :)

I'm currently mocking some outputs using MockComponent. There seems to be an issue where all instances of the mock component share the same event emitter. Calling emit on a single instance will therefore call the bound method multiple times, once for each instance.

Here's a test to demonstrate this:

import { Component, EventEmitter, Output } from "@angular/core";
import { createTestComponentFactory, MockComponent } from "@netbasal/spectator";

@Component({
    selector: "app-subcomponent-component",
    template: ``,
})
class SubcomponentDemoComponent {
    @Output()
    public emitter = new EventEmitter();
}

@Component({
    selector: "app-demo-component",
    template: `
        <app-subcomponent-demo (emitter)="boundMethod()"></app-subcomponent-demo>
        <app-subcomponent-demo (emitter)="boundMethod()"></app-subcomponent-demo>
    `,
})
class DemoComponent {
    public boundMethod() {}
}

describe("spectator", () => {
    const create = createTestComponentFactory({
        component: DemoComponent,
        shallow: true,
        declarations: [
            MockComponent({
                selector: "app-subcomponent-demo",
                identifier: SubcomponentDemoComponent,
                outputs: ["emitter"],
            }),
        ],
    });

    it("should differentiate between instances of event emitters", () => {
        const spectator = create({}, false);
        spyOn(spectator.component, "boundMethod");

        const instances = spectator.queryAll<SubcomponentDemoComponent>(SubcomponentDemoComponent);
        instances[0].emitter.emit();
        expect(spectator.component.boundMethod).toHaveBeenCalledTimes(1);
    });
});

The function, boundMethod should only be called once because only one value is emitted, however, it is called twice.

Could you have a look into this?

Thanks and cheers,
Oliver

[feat] mockProvider for properties

Imagine testing a service depending on observable from another service:

@Injectable({
  providedIn: 'root'
})
export class ConsumerService {
   constructor(private serviceWithObservable: ServiceWithObservable) {
      serviceWithObservable.obs$.subscribe(...)
   }
}

@Injectable({
  providedIn: 'root'
})
export class ServiceWithObservable {
   obs$ = new Subject();
}

For testing services we use mocks array which calls mockProvider which works great but it creates spies only for methods. Observable property is missing so in the test you need to do something like:

const serviceWithObservable = TestBed.get(ServiceWithObservable) as ServiceWithObservable;
serviceWithObservable.obs$ = new Subject();

- What do you think about adding parameter for properties to mockProvider?

Eg:

providers: [
   mockProvider(ServiceWithObservable, {
      obs$: new Subject()
   })
]

Could be used with Object.assign or something to add these properties to the mock.

How do you test HTTP error conditions?

I have a very simple service that runs a very simple POST.

   return this.http.post<SearchRequest>(`/api/2.0/search/userproduct/${userProductId}`, request)
      .pipe(
        catchError((e: HttpErrorResponse) => {
          Logger.error('SearchService', 'search', e.message);
          return of(undefined);
        })
      );

I can easily cause an error to happen by calling TestResponse.error(''); But this doesn't allow me to test that I returned something. How do I write the test that tests for receiving undefined in an error condition? And what if I handled a 500 differently from a 204 or 401/3?

createService doesn't accept declarations

At first glance it makes perfect sense that createService doesn't need declarations array however when testing a service depending on RouterModule there is a need to define routes. And routes must lead to a component.

I could mock Router and that would solve my problem. However that means also mimicking internal behaviour of Router.

What do you think about allowing declarations also for createService?

Add docs on how to use MockComponent with output

So i wanted to unit test a component that has an event handler for a child component. I don't want to test the child component, so i mocked the child component using MockComponent({ selector: 'foo', output: ['change'] }).

I looked at the spectator sources and saw that it created an EventEmitter for every output. Ok so far, but how do i get the mock component instance and emit the event?

      const component = spectator.debugElement.query(By.css('foo')).componentInstance;
      component.change.emit('foo');

      expect(service.update).toHaveBeenCalledWith('foo');

Afaics there is no easier way to do this using spectator? Also, perhaps we should document this? :)

Idea: TestFixture generator

Today, when writing unit tests, I wanted to improve the organisation of our testdata. I came up with a simple test fixture generator:

// Example domain model
interface Person {
  id: string;
  name: string;
  dateStarted: number;
  activated?: boolean;
  department: Department;
}

interface Department {
  id: string;
  name: string;
}
// Fixture factory creator
function createFixtureFactory<T>(defaultObject: T): (overrideObject?: Partial<T>) => T {
  return (overrideObject: Partial<T> = {}) => {
    return Object.assign({}, defaultObject, overrideObject);
  };
}
// Example fixture factories
class DepartmentFixtures {
  static createDefault = createFixtureFactory<Department>({
    id: '1',
    name: 'foo'
  })
}

class PersonFixtures {
  static createDefault = createFixtureFactory<Person>({
    id: '1',
    activated: true,
    dateStarted: 1,
    name: 'foo',
    department: DepartmentFixtures.createDefault()
  });

  static createCrasyPerson = createFixtureFactory(PersonFixtures.createDefault({
    name: 'crasy'
  }));
}
// example test
describe('', () => {
  const testPerson1 = PersonFixtures.createDefault({
    id: '1',
    department: DepartmentFixtures.createDefault({
      name: 'joe'
    })
  });

  const testPerson2 = PersonFixtures.createDefault({
    id: '2',
    department: DepartmentFixtures.createDefault({ 
      name: 'joe'
    })
  });
});

The benefits:

  • Full type completion, no any objects in unit tests
  • Reuse of default data, with full freedom in changing specific fields
  • Readable factory methods

What about adding such feature to Spectator? Or should it remain in a separate util library?

ping @NetanelBasal

Complex Input properties

Great library! Looking forward to trying it out.

In your example, you have:

  host = createHost(`<zippy title="Zippy title"></zippy>`);

I was curious to know if you've thought of a way to pass complex objects as input parameters instead of just simple primitives. I occasionally create components that require this, usually receiving those objects from another component.

For example, It could be useful to be able to do something like this:

  host = createHost(`<zippy title="Zippy title" [options]="options"></zippy>`, { options: ... });

Thanks!

Setting up a host for a directive with providers

Problem

I have a use case where a directive makes use of a service.
However, I can not figure out how to set this up with createHostComponentFactory.

I've used AppUnlessDirective from the Angular docs as an example.

const createHost = createHostComponentFactory({
  component: AppUnlessDirective,
  providers: [AppUnlessService]
});

Giving the following error (because it's a directive and not a component?)

TypeError: Cannot read property 'componentInstance' of null

Example code

https://stackblitz.com/edit/spectator-directive-providers?file=app%2Fapp-unless.directive.spec.ts

Add option to defer initial host change detection

I've been trying to test a directive with EasyTestWithHost and it's fantastic for all my cases save one -- I have one test case where I need to set-up a spy which I know should get triggered in the initial change detection cycle (in ngAfterContentInit in my case).

If I do TestBed.get(token) before I called this.create() (in order to spy on a method), I get an error saying overrideComponent cannot be called after inject which makes sense. Ideally I'd like to have an option for this.create() which suppresses the first change detection in the host so that I can call it once I've created my spy.

Looking at the code, this may not be a trivial change as the change detection may be needed for other aspects of the test set-up. I'll have a play if I get time and submit a PR.

Incorrect keyboard event creation

So when i use the spectator API to dispatch a keydown event like this:

spectator.dispatchKeyboardEvent(select, 'keydown', SPACE);

Things do not work correctly. After debugging the code, it turns out that event.metaKey is true for the dispatched event (running in Chrome). After some looking, i think the bug is here: https://github.com/NetanelBasal/spectator/blob/master/projects/spectator/src/lib/event-objects.ts#L40

When i look at MDN, the API for initKeyEvent is different from initKeyboardEvent. The spectator code doesn't seem to take that into account. Also, according to MDN both methods are deprecrated and you should just use the new KeyboardEvent(...) constructor function.

> ev = document.createEvent('KeyboardEvent')
KeyboardEvent {isTrusted: false, key: "", code: "", location: 0, ctrlKey: false, …}
> ev.initKeyboardEvent('keydown', true, true, window, 0, 0, 0, 0, 0, 13)
undefined
> ev.metaKey
true

Missing Type Definition File for @types/je st

Created new angular-cli version 7 project and did a test drive using Spectator.
Received an error when running ng test:

ERROR in node_modules/@netbasal/spectator/lib/mock.d.ts(1,23): error TS2688: Cannot find type definition file for '@types/jest'.

Installing @types/jest does not help, since I have inference with jasmine types (want to stick to Jasmine)

Any help is appreciated

expectOne: Spec has no expectations

When testing data service using createHTTPFactory I see errors in the console saying that the test has no expectations. Looks like expectOne is not connected to jasmine.

Simple fix would be to add expect(true).toBe(true); to expectOne but I am not sure if it's the correct one.

Tried with spectator 3.3.0 but it behaved same with the older version.

How to use MockComponent with ngModelChange output

I'm trying to test a component that calls a service when a child component updates using (ngModelChange)="update($event)". However the typescript compiler doesn't recognize the ngModelChange output on the component (there is no property on the component class).

error TS2339: Property 'ngModelChange' does not exist on type 'EditAvatarComponent'.

Caused by:

  fit('should update the user when the avatar changes', () => {
    const component = spectator.query<EditAvatarComponent>(EditAvatarComponent);
    component.ngModelChange.emit({ image: 'foo' });

    expect(currentUserService.update).toHaveBeenCalledWith({ avatar: { image: 'foo' } });
  });

Any ideas?

typeInElement and ngModel

I have a component with a form. However, the typeInElement() function does not seem to work with the ngModel directive. I tried solving it with fakeAsync and tick() but the form control stays empty, and so does the model.

My component (simplified):

@Component({
  selector: 'app-foo',
  template: '<form><input type="text" name="foo" class="foo" [(ngModel)]="model.foo" /></form>'
})
class FooComponent {
  model = {
    foo: ''
  };
}

My test code (simplified):

describe('FooComponent', () => {
  const createComponent = createTestComponentFactory({
    component: FooComponent,
    imports: [FormsModule],
  });

  let spectator: Spectator<FooComponent>;

  it('should search when submitting form', fakeAsync(() => {
    spectator = createComponent();

    spectator.typeInElement('My value', '.foo');
    
    // tick(); // I tried this
    // spectator.detectChanges(); // ...and this
    
    expect(spectator.component.model.foo).toBe('My value'); // fails
  }));
});

Any help on this?

Cannot test a component with nested service dependency

Following your Widget Example,
Lets Say I have a Test Component A that depends on Service B, where Service B also depends on Service C. If i add the service B to Mocks, i still get the error saying "No provider for Service C". Isn't is supposed to mock the dependency tree? How can i solve this error?

Test Error
screen shot 2018-05-24 at 12 51 59 pm

Spec File
screen shot 2018-05-24 at 12 49 08 pm

Component File
screen shot 2018-05-24 at 12 49 53 pm

Core Service
screen shot 2018-05-24 at 12 34 41 pm

Add schematics support

  1. Create ng add @netbasal/spectator command.
  2. Change the basic spec file that the CLI generates to Spectator template.

Not compatible with rxjs 6

...without adding the rxjs-compat module (which is meant to be a temporary workaround).

ERROR in node_modules/@netbasal/spectator/dist/src/internals.d.ts(10,10): error TS2305: Module '".../node_modules/rxjs/Observable"' has no exported member 'Observable'.
node_modules/rxjs/Observable.d.ts(1,15): error TS2307: Cannot find module 'rxjs-compat/Observable'

Configure type auto completion for IDE

Hi,

I want to configure typing autocompletion when using your library but I couldn't find anything in the README. Would you please tell me how to configure it ?

Error when using multiple factories

Just wanted to report that in some situations this error can appear:

Illegal state: Could not load the summary for directive HostComponent.

This happens when you use multiple factories (e.g. createTestComponentFactory and createHostComponentFactory) in the same test ("describe") block.

Is this intended? Maybe a better error message should be visible.

Project isn't aligned with its own set standards

The code is mentioned to be formatted using Prettier, and uses TSLint. However, the code within it is not conforming to the latter, and the use of Prettier's formatting isn't automatic via a pre-commit hook.

HttpFactory expectOne failing when params send with GET request

For a filter endpoint I have to add query params to a GET request.
Before this expectOne('endpoint/to/call', HTTPMethod.GET) was working great, but now I have added params to the request and suddenly it stops working and logs Expected one matching request for criteria "Match method: GET, URL: 'endpoint/to/call", found none.. I also see a logging line saying Expected no open requests, found 1: GET endpoint/to/call

The HTTP call before:

return this.http.get<SomeObject>(`endpoint/to/call`);

The HTTP call after:

const params = new HttpParams({
    fromString: 'value1=test&value2=testing'
  });
return this.http.get<SomeObject>(`endpoint/to/call`, {params: params});

Thanks!

dispatchKeyboardEvent doesn't work as expected

Adding the following to ZippyComponent:

<div (click)="toggle()" class="zippy__title" (keyup.enter)="toggle()">

and adding the following test:

it('should toggle the content when pressing "Enter"', () => {
    host = createHost(`<zippy title="Zippy title"></zippy>`);

    const pressEnter = () => {
      host.dispatchKeyboardEvent('.zippy__title', 'keyup', /* Enter key */ 13);
    };

    pressEnter();
    expect(host.query(".zippy__content")).toExist();

    pressEnter();
    expect(".zippy__content").not.toExist();
  });

Will fail, though it's expected to succeed.

The following alternative code in (plain) Angular works (notice the differences in the pressEnter function):

it('should toggle the content when pressing "Enter"', () => {
    host = createHost(`<zippy title="Zippy title"></zippy>`);

    const pressEnter = () => {
      host.getNativeElement('.zippy__title').dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter' }));
      host.detectChanges();
    };

    pressEnter();
    expect(host.query(".zippy__content")).toExist();

    pressEnter();
    expect(".zippy__content").not.toExist();
  });

Option to use Jest for Spectator mocking

We successfully migrated from Karma/Jasmine to Jest (with Spectator unit tests).

Spectator uses Jasmine for the mocking implementation, this still works fine. However, as Jest has its own mocking API, is it possible to make Spectator use Jest for mocks? This prevents mixing two different API's for mocking.

e.g.

const createComponent = createTestComponentFactory({
  component: MyComponent,
  mocks: [MyService]
});

let spectator: Spectator<MyComponent>;

beforeEach(() => {
  spectator = createComponent();

  // spectator.get(MyService).someMethod.andReturn('foo'); // with Jasmine
  spectator.get(MyService).someMethod.mockReturnValue('foo'); // with Jest
});

Maybe by checking if Jest is available and automatically use Jest instead of Karma, or by configuring Spectator somehow?

I am willing to contribute for this feature request.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.