npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

ng-test-kit

v0.0.1

Published

NG Test Tools is an opinionated library for Angular component tests based on Jasmine. It provides tools for bootstrapping tests and helpers for controlling the TestBed and the Component Harnesses.

Downloads

2

Readme

NG Test Tools

NG Test Tools is an opinionated library for Angular component tests based on Jasmine. It provides tools for bootstrapping tests and helpers for controlling the TestBed and the Component Harnesses.

The premise

The best way to test a component is to treat it as one single unit including its code, template and style. This unit has a set of inputs (its public API and UI events) and a set of outputs (outbound event emitters and UI changes). Therefore, when we test this unit we should focus on modifying and triggering its inputs and then monitoring its outputs. Our tests should target behavior rather than implementation. The end result is a test that can only break when the component's behavior and/or API is changed.

The method

To test a component's behavior we wrap it in a host component and drive it by modifying it's inputs. We do that by changing host properties that are bound to the component's inputs.

We analyze the behavior by spying on host callbacks that are bound to the component's outputs and monitor UI changes by watching the associated ComponentHarness.

The details

The following information demonstrates how to set up a test for a simple component using this library. We are going to deal with a simple expander component. This component has a title. When clicking the title it expands a panel with more content. Clicking the title a second time closes that panel.

Setting up

First, we need a host component to load our expander component.

@Component({
  template: `
    <lib-expander [title]="titleText">
      {{ content }}
    </lib-expander>
  `,
})
class HostComponent {
  titleText: string;
  content: string;
}

Next, we write our describe function and add the TestContext. The TestContext.create() takes one argument which is our host component. If we were testing a directive our setup would end here:

describe('ExpanderComponent', () => {
  const context = TestContext.create(HostComponent).build();

  beforeEach(async () => {
    await context.bootstrap();
  });

  it('should create', () => {
    expect(context.host).toBeDefined();
  });
});

However, we are testing a component and would like our TestContext to be aware of it. Let's add it.

describe('ExpanderComponent', () => {
  const context = TestContext.create(HostComponent)
    .withComponent(ExpanderComponent) // <-- adding the component
    .build();

  beforeEach(async () => {
    await context.bootstrap();
  });

  it('should create', () => {
    expect(context.component).toBeDefined();
  });
});

If our component has dependencies we can provide a module metadata that includes all the providers (and mocks) that are needed.

describe('ExpanderComponent', () => {
  const context = TestContext.create(HostComponent)
    .withComponent(ExpanderComponent) // <-- adding the component
    .withMetaData({
      imports: [SomeModule],
      providers: [SomeProvider],
      declarations: [HostComponent],
    })
    .build();

  beforeEach(async () => {
    await context.bootstrap();
  });

  it('should create', () => {
    expect(context.component).toBeDefined();
  });
});

If we need to run some code before the library calls TestBed.compileComponents() then we can use runBeforeTestBedCompile.

const context = TestContext.create(HostComponent)
  .withComponent(ExpanderComponent)
  .runBeforeTestBedCompile(() => {
    // here goes code that runs in a beforeEach
  })
  .build();

We can also call bootstrapStable if our component triggers some zone tasks in its initialization code.

const context = TestContext.create(HostComponent).withComponent(ExpanderComponent).build();

beforeEach(async () => {
  await context.bootstrapStable();
});

Our context is set up and we are ready to write some tests!

#### The TestContext builder API
  • create(hostComponent: Type<THost>) - creates a context for the host component provided
  • withComponent(component: Type<TComponent>) - adds access to the component instance
  • withHarness<T extends ComponentHarness>(harness: T) - instantiates a ComponentHarness with the type provided and adds access to it
  • withMetaData(metadata: TestModuleMetadata) - overrides the default module metadata used for the test
  • useStableZone() - waits for any async tasks triggered by component initiation to complete
  • runBeforeCompile(func: ()=>void) - allows to run code in a beforeEach statement before calling TestBed.compileComponents()
  • build() - builds a new text context for us to use
  • bootstrap() - compiles and resets context fields with the newly created fixture

Working with the TestContext

The context we created above contains a few properties and utility methods for commonly used actions.

Properties

  • component - holds the component instance created after compilation (will be undefined if we didn't use withComponent)
  • element - holds the reference to the HTML element holding the component
  • fixture - holds the reference to the TestFixture that was created by the TestBed
  • host - holds the host instance created after compilation
  • harness - holds the reference for the ComponentHarness instance that was created by the library (will be undefined if we didn't use withHarness)

Methods

  • detectChanges - a shortcut to fixture.detectChanges
  • setHostProp(propObject, callDetectChanges) - a helper function to modify host properties and an option to call detectChanges as the 2nd parameter.

Working with Harnesses

A ComponentHarness is a representation of component elements through code in an object oriented way. To set up a ComponentHarness we create a class and extend the CDK's ComponentHarness class.

class ExpanderHarness extends ComponentHarness {
  statis hostSelector = '.app-expander';
  public getTitleElement = this.loaderFor('.app-expander__title');
}

For more information about using Harnesses see the Angular CDK Harnesses.

In order to use our Harness we first need to tell our TestContext about it.

const context = TestContext.create(HostComponent).withComponent(ExpanderComponent).withHarness(ExpanderHarness).bootstrap();

Then, in our tests, we can use this Harness to navigate to the element we are monitoring.

it('should show the correct title', async () => {
  const title = await ctx.harness.getTitleElement();
  const titleText = await title.text();
  expect(titleText).toBe(ctx.host.titleText);
});