Skip to content

getsaf/shallow-render

Repository files navigation

shallow-render

CircleCInpm version

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

Schematics

Articles

Angular Version Support

Angularshallow-render
20x20x
19x19x
18x18x
17x17x
16x16x
15x15x
14x14x
13x13x
12x12x
11x11x
10x10x
9x9x
6x-8x8x
5x<= 7.2.0

Super Simple Tests

describe('ColorLinkComponent',()=>{letshallow: Shallow<ColorLinkComponent>;beforeEach(()=>{shallow=newShallow(ColorLinkComponent,MyModule);});it('renders a link with the name of the color',async()=>{const{ find }=awaitshallow.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 }=awaitshallow.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:

describe('MyComponent',()=>{beforeEach(async=>{returnTestBed.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(()=>{letmyService=TestBed.get(MyService);// Not type safespyOn(myService,'foo').and.returnValue('mocked foo');});});it('renders a link with the provided label text',()=>{constfixture=TestBed.createComponent(TestHostComponent);fixture.componentInstance.labelText='my text';fixture.detectChanges();constlink=fixture.debugElement.query(By.css('a'));expect(a.nativeElement.textContent).toBe('my text');});it('sends "foo" to bound click events',()=>{constfixture=TestBed.createComponent(TestHostComponent);spyOn(fixture.componentInstance,'handleClick');fixture.detectChanges();constmyComponentElement=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>',})classTestHostComponent{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:

describe('MyComponent',()=>{letshallow: Shallow<MyComponent>;beforeEach(()=>{shallow=newShallow(MyComponent,MyModule);});it('renders a link with the provided label text',async()=>{const{ find }=awaitshallow.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 }=awaitshallow.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.

About

Angular testing made easy with shallow rendering and easy mocking. https://getsaf.github.io/shallow-render

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Packages

No packages published

Contributors 12