UNPKG

shallow-render

Version:

Shallow rendering test utility for Angular

184 lines (140 loc) 6.81 kB
# shallow-render [![CircleCI](https://circleci.com/gh/getsaf/shallow-render.svg?style=svg)](https://circleci.com/gh/getsaf/shallow-render) [![npm version](https://badge.fury.io/js/shallow-render.svg)](https://www.npmjs.com/package/shallow-render) Angular testing made easy with shallow rendering and easy mocking. --- # Looking For Help Maintaining This Library! Hey all, I do plan on continuing to update this library as Angular provides updates. This is not easy for me to do as I don't work with Angular in my current job. Any additonal support would be very appreciated! Please reach out if you want to be a help! ## Docs - [API Docs](https://getsaf.github.io/shallow-render) - [Example Tests](https://github.com/getsaf/shallow-render/tree/master/lib/examples) - [Release Notes](https://github.com/getsaf/shallow-render/releases) ## Schematics - [Angular Schematics](https://github.com/mihalcan/shallow-render-shematics) (thanks @mihalcan!) ## Articles - [Testing Angular Components With shallow-render](https://medium.com/@getsaf/testing-angular-components-with-shallow-render-9334d16dc2e3?source=friends_link&sk=5c72c2bf4ce91da656916dc680f8b1cf) - [Advanced shallow-render Testing](https://medium.com/@getsaf/advanced-shallow-render-testing-for-angular-components-452ce74d5f88?source=friends_link&sk=91d48511b60871c7b34b1bbb231ce1a5) - [Why Shallow Rendering is Important](https://medium.com/@getsaf/why-shallow-rendering-is-import-in-angular-unit-tests-84569d571b72?source=friends_link&sk=4576570c948a531036cc8fe9e2dc9a19) ## Angular Version Support | Angular | shallow-render | | ------- | -------------- | | 20x | 20x | | 19x | 19x | | 18x | 18x | | 17x | 17x | | 16x | 16x | | 15x | 15x | | 14x | 14x | | 13x | 13x | | 12x | 12x | | 11x | 11x | | 10x | 10x | | 9x | 9x | | 6x-8x | 8x | | 5x | <= 7.2.0 | ## Super Simple Tests ```typescript describe('ColorLinkComponent', () => { let shallow: Shallow<ColorLinkComponent>; beforeEach(() => { shallow = new Shallow(ColorLinkComponent, MyModule); }); it('renders a link with the name of the color', async () => { const { find } = await shallow.render({ bind: { color: 'Blue' } }); // or shallow.render(`<color-link color="Blue"></color-link>`); expect(find('a').nativeElement.textContent).toBe('Blue'); }); it('emits color when clicked', async () => { const { element, outputs } = await shallow.render({ bind: { color: 'Red' } }); element.click(); expect(outputs.handleClick.emit).toHaveBeenCalledWith('Red'); }); }); ``` ## The problem Testing in Angular is **HARD**. TestBed is powerful but its use in component specs ends with lots of duplication. Here's a standard TestBed spec for a component that uses a few other components, a directive and a pipe and handles click events: ```typescript describe('MyComponent', () => { beforeEach(async => { return TestBed.configureTestModule({ imports: [SomeModuleWithDependencies], declarations: [ TestHostComponent, MyComponent, // <-- All I want to do is test this!! // We either must list all our dependencies here // -- OR -- // Use NO_ERRORS_SCHEMA which allows any HTML to be used // even if it is invalid! ButtonComponent, LinkComponent, FooDirective, BarPipe, ], providers: [MyService], }) .compileComponents() .then(() => { let myService = TestBed.get(MyService); // Not type safe spyOn(myService, 'foo').and.returnValue('mocked foo'); }); }); it('renders a link with the provided label text', () => { const fixture = TestBed.createComponent(TestHostComponent); fixture.componentInstance.labelText = 'my text'; fixture.detectChanges(); const link = fixture.debugElement.query(By.css('a')); expect(a.nativeElement.textContent).toBe('my text'); }); it('sends "foo" to bound click events', () => { const fixture = TestBed.createComponent(TestHostComponent); spyOn(fixture.componentInstance, 'handleClick'); fixture.detectChanges(); const myComponentElement = fixture.debugElement.query(By.directive(MyComponent)); myComponentElement.click(); expect(fixture.componentInstance.handleClick).toHaveBeenCalledWith('foo'); }); }); @Component({ template: '<my-component [linkText]="linkText" (click)="handleClick($event)"></my-component>', }) class TestHostComponent { linkLabel: string; handleClick() {} } ``` Whew!!! That was a lot of boilerplate. Here's just some of the issues: - Our TestBed module looks very similar if not identical to the `NgModule` I've probably already added `MyComponent` too. Total module duplication. - Since I've duplicated my module in my spec, I'm not actually sure the real module was setup correctly. - I've used REAL components and services in my spec which means I have not isolated the component I'm interested in testing. - This also means I have to follow, and provide all the dependencies of those real components to the `TestBed` module. - I had to create a `TestHostComponent` so I could pass bindings into my actual component. - My `TestBed` boilerplate code-length exceeded my actual test code-length. ## The Solution We should mock everything we can except for the component in test and that should be **EASY**. Our modules already define the environment in which our components live. They should be _reused_, not _rebuilt_ in our specs. Here's the same specs using `shallow-render`: ```typescript describe('MyComponent', () => { let shallow: Shallow<MyComponent>; beforeEach(() => { shallow = new Shallow(MyComponent, MyModule); }); it('renders a link with the provided label text', async () => { const { find } = await shallow.render({ bind: { linkText: 'my text' } }); // or shallow.render(`<my-component linkText="my text"></my-component>`); expect(find('a').nativeElement.textContent).toBe('my text'); }); it('sends "foo" to bound click events', async () => { const { element, outputs } = await shallow.render(); element.click(); expect(outputs.handleClick).toHaveBeenCalledWith('foo'); }); }); ``` Here's the difference: - Reuses (and verifies) `MyModule` contains your component and all its dependencies. - All components inside `MyModule` are mocked. This is what makes the rendering "shallow". - The tests have much less boilerplate which makes the specs easier to follow. - The HTML used to render the component is IN THE SPEC and easy to find. - This means specs now double examples of how to use your component.