Shallow rendering test utility for Angular
npm install shallow-render

Angular testing made easy with shallow rendering and easy mocking.
---
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!
- API Docs
- Example Tests
- Release Notes
- Angular Schematics (thanks @mihalcan!)
- Testing Angular Components With shallow-render
- Advanced shallow-render Testing
- Why Shallow Rendering is Important
| 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 |
``typescript
describe('ColorLinkComponent', () => {
let shallow: Shallow
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();
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');
});
});
`
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: '
})
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.TestBed
- 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 module.TestHostComponent
- I had to create a so I could pass bindings into my actual component.TestBed
- My boilerplate code-length exceeded my actual test code-length.
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
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();
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.MyModule` are mocked. This is what makes the rendering "shallow".
- All components inside
- 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.