Skip to content

Commit

Permalink
fix: a contemporary example
Browse files Browse the repository at this point in the history
  • Loading branch information
satanTime committed Nov 29, 2020
1 parent 62fa4ed commit d0e5efc
Show file tree
Hide file tree
Showing 132 changed files with 4,660 additions and 1,780 deletions.
12 changes: 12 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Editor configuration, see http://editorconfig.org
root = true

[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
max_line_length = 120
tab_width = 2
trim_trailing_whitespace = true
3 changes: 3 additions & 0 deletions .prettierrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,6 @@ overrides:
- files: 'README.md'
options:
printWidth: 70
- files: '*.spec.ts'
options:
printWidth: 70
105 changes: 47 additions & 58 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
[![coverage status](https://img.shields.io/coveralls/github/ike18t/ng-mocks/master)](https://coveralls.io/github/ike18t/ng-mocks?branch=master)
[![language grade](https://img.shields.io/lgtm/grade/javascript/g/ike18t/ng-mocks)](https://lgtm.com/projects/g/ike18t/ng-mocks/context:javascript)

# ng-mocks - ease of creating mock declarations out of annoying dependencies in Angular unit tests
# Create mock components and more out of annoying dependencies in Angular tests

`ng-mocks` is a library for testing Angular 5+ applications which provides helper functions for **creating mock components**, directives, pipes, modules and services.
Whether you need a **mock child component**, or any other annoying dependency, `ng-mocks` has a tool to turn this declaration into its mock copy,
`ng-mocks` is a library which provides helper functions for
**creating mock components**, directives, pipes, modules and services
for easy testing of Angular 5+ applications.
Whether you need a **mock child component**, or any other annoying dependency,
`ng-mocks` has a tool to turn this declaration into its mock copy,
keeping its interface as it is, but suppressing its implementation.

The current version of the library has been tested and can be used with:
Expand Down Expand Up @@ -383,7 +386,6 @@ describe('MockComponent', () => {
// ).componentInstance
// but properly typed.
const mockComponent = ngMocks.find<DependencyComponent>(
fixture,
'app-child',
).componentInstance;

Expand All @@ -407,7 +409,7 @@ describe('MockComponent', () => {
// By.directive(DependencyComponent)
// ).componentInstance
// but properly typed.
const mockComponent = ngMocks.find(fixture, DependencyComponent)
const mockComponent = ngMocks.find(DependencyComponent)
.componentInstance;

// Again, let's pretend DependencyComponent has an output
Expand Down Expand Up @@ -463,10 +465,8 @@ describe('MockComponent', () => {

// The rendered template is wrapped by <div data-key="something">.
// We can use this selector to assert exactly its content.
const mockNgTemplate = ngMocks.find(
fixture.debugElement,
'[data-key="something"]',
).nativeElement.innerHTML;
const mockNgTemplate = ngMocks.find('[data-key="something"]')
.nativeElement.innerHTML;
expect(mockNgTemplate).toContain('<p>inside template</p>');
});
});
Expand Down Expand Up @@ -574,7 +574,7 @@ describe('MockDirective:Attribute', () => {
// ).injector.get(DependencyDirective)
// but easier and more precise.
const mockDirective = ngMocks.get(
ngMocks.find(fixture.debugElement, 'span'),
ngMocks.find('span'),
DependencyDirective,
);

Expand All @@ -599,7 +599,7 @@ describe('MockDirective:Attribute', () => {
// ).injector.get(DependencyDirective)
// but easier and more precise.
const mockDirective = ngMocks.get(
ngMocks.find(fixture.debugElement, 'span'),
ngMocks.find('span'),
DependencyDirective,
);

Expand Down Expand Up @@ -648,24 +648,19 @@ describe('MockDirective:Structural', () => {

// Let's assert that nothing has been rendered inside of
// the structural directive by default.
expect(
fixture.debugElement.nativeElement.innerHTML,
).not.toContain('>content<');
expect(fixture.nativeElement.innerHTML).not.toContain(
'>content<',
);

// And let's render it manually now.
const mockDirective = ngMocks.findInstance(
fixture.debugElement,
DependencyDirective,
);
const mockDirective = ngMocks.findInstance(DependencyDirective);
if (isMockOf(mockDirective, DependencyDirective, 'd')) {
mockDirective.__render();
fixture.detectChanges();
}

// The content of the structural directive should be rendered.
expect(fixture.debugElement.nativeElement.innerHTML).toContain(
'>content<',
);
expect(fixture.nativeElement.innerHTML).toContain('>content<');
});
});
```
Expand Down Expand Up @@ -763,8 +758,9 @@ describe('MockPipe', () => {
it('transforms values to json', () => {
const fixture = MockRender(TestedComponent);

const pipeElement = ngMocks.find(fixture.debugElement, 'span');
expect(pipeElement.nativeElement.innerHTML).toEqual('["foo"]');
expect(fixture.nativeElement.innerHTML).toEqual(
'<component>["foo"]</component>',
);
});
});
```
Expand Down Expand Up @@ -1112,17 +1108,16 @@ If we created a fixture, we would face an error about reading properties of `und
returns a spy, if [auto spy](#auto-spy) has been configured, or `undefined`. Therefore, neither has the `subscribe` property.

Obviously, to solve this, we need to get the method to return an observable stream.
For that, we could create a mock copy via [`MockService`](#how-to-create-a-mock-service) and to pass it as the second parameter into [`MockProvider`](#how-to-create-a-mock-provider).
For that, we could extend a mock copy via passing overrides as the second parameter into [`MockProvider`](#how-to-create-a-mock-provider).

```typescript
const todoServiceMock = MockService(TodoService);
ngMocks.stub(todoServiceMock, {
list$: () => EMPTY,
});

TestBed.configureTestingModule({
declarations: [TodoComponent],
providers: [MockProvider(TodoService, todoServiceMock)],
providers: [
MockProvider(TodoService, {
list$: () => EMPTY,
}),
],
});
```

Expand All @@ -1138,14 +1133,14 @@ let todoServiceList$: Subject<any>; // <- a context variable.

beforeEach(() => {
todoServiceList$ = new Subject(); // <- create the subject.
const todoServiceMock = MockService(TodoService);
ngMocks.stub(todoServiceMock, {
list$: () => todoServiceList$,
});

TestBed.configureTestingModule({
declarations: [TodoComponent],
providers: [MockProvider(TodoService, todoServiceMock)],
providers: [
MockProvider(TodoService, {
list$: () => todoServiceList$,
}),
],
});
});

Expand All @@ -1164,15 +1159,10 @@ let todoServiceList$: Subject<any>; // <- a context variable.

beforeEach(() => {
todoServiceList$ = new Subject(); // <- create the subject.
const todoServiceMock = MockService(TodoService);
ngMocks.stub(todoServiceMock, {

return MockBuilder(TodoComponent).mock(TodoService, {
list$: () => todoServiceList$,
});

return MockBuilder(TodoComponent).mock(
TodoService,
todoServiceMock,
);
});

it('test', () => {
Expand Down Expand Up @@ -1246,6 +1236,11 @@ describe('MockObservable', () => {
expect(fixture.nativeElement.innerHTML).not.toContain('1');
expect(fixture.nativeElement.innerHTML).not.toContain('2');
expect(fixture.nativeElement.innerHTML).not.toContain('3');

// Checking that a sibling method has been replaced
// with a mock copy too.
expect(TestBed.inject(TargetService).getValue$).toBeDefined();
expect(TestBed.inject(TargetService).getValue$()).toBeUndefined();
});
});
```
Expand Down Expand Up @@ -1289,10 +1284,8 @@ describe('MockReactiveForms', () => {
const component = fixture.point.componentInstance;

// Let's find the mock form component.
const mockControl = ngMocks.find(
fixture.debugElement,
DependencyComponent,
).componentInstance;
const mockControl = ngMocks.find(DependencyComponent)
.componentInstance;

// Let's simulate its change, like a user does it.
if (isMockOf(mockControl, DependencyComponent, 'c')) {
Expand Down Expand Up @@ -1334,10 +1327,8 @@ describe('MockForms', () => {
const component = fixture.point.componentInstance;

// Let's find the mock form component.
const mockControl = ngMocks.find(
fixture.debugElement,
DependencyComponent,
).componentInstance;
const mockControl = ngMocks.find(DependencyComponent)
.componentInstance;

// Let's simulate its change, like a user does it.
if (isMockOf(mockControl, DependencyComponent, 'c')) {
Expand Down Expand Up @@ -1426,9 +1417,7 @@ class AppComponent {
})
class AppHeaderComponent {
@Output() public readonly logo = new EventEmitter<void>();

@ContentChild('menu') public menu?: TemplateRef<ElementRef>;

@Input() public showLogo = false;
@Input() public title = '';
}
Expand Down Expand Up @@ -1612,12 +1601,11 @@ and has a rich toolkit that supports:
The source file is here:
[MockBuilder](https://github.com/ike18t/ng-mocks/blob/master/examples/MockBuilder/test.spec.ts).<br>
Prefix it with `fdescribe` or `fit` on
[codesandbox.io](https://codesandbox.io/s/github/ng-mocks/examples?file=/src/examples/MockBuilder/test.spec.ts)
[codesandbox.io](https://codesandbox.io/s/github/ng-mocks/examples?file=/src/examples/MockBuilder/test.simple.spec.ts)
to play with.

```typescript
describe('MockBuilder:simple', () => {
// Do not forget to return the promise of MockBuilder.
beforeEach(() => MockBuilder(MyComponent, MyModule));
// The same as
// beforeEach(() => TestBed.configureTestingModule({{
Expand All @@ -1629,7 +1617,7 @@ describe('MockBuilder:simple', () => {
it('should render content ignoring all dependencies', () => {
const fixture = MockRender(MyComponent);
expect(fixture).toBeDefined();
expect(fixture.debugElement.nativeElement.innerHTML).toContain(
expect(fixture.nativeElement.innerHTML).toContain(
'<div>My Content</div>',
);
});
Expand Down Expand Up @@ -2110,6 +2098,9 @@ describe('MockRender', () => {

it('renders inputs and outputs automatically', () => {
const spy = jasmine.createSpy();
// in case of jest
// const logoClickSpy = jest.fn();

// Generates a template like:
// <tested [value1]="value1" [value2]="value2"
// (trigger)="trigger"></tested>.
Expand Down Expand Up @@ -2271,9 +2262,7 @@ describe('MockInstance', () => {
it('should render', () => {
// Without the custom initialization rendering would fail here
// with "Cannot read property 'subscribe' of undefined".
expect(() => MockRender(RealComponent)).not.toThrowError(
/Cannot read property 'subscribe' of undefined/,
);
expect(() => MockRender(RealComponent)).not.toThrow();
});
});
```
Expand Down
34 changes: 26 additions & 8 deletions examples/MAIN/test.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
// tslint:disable arrow-return-shorthand
// tslint:disable arrow-return-shorthand strict-type-predicates

import { CommonModule } from '@angular/common';
import { Component, ContentChild, ElementRef, EventEmitter, Input, NgModule, Output, TemplateRef } from '@angular/core';
import {
Component,
ContentChild,
ElementRef,
EventEmitter,
Input,
NgModule,
Output,
TemplateRef,
} from '@angular/core';
import { RouterModule } from '@angular/router';
import { MockBuilder, MockRender, ngMocks } from 'ng-mocks';

Expand All @@ -11,7 +20,11 @@ import { staticFalse } from '../../tests';
@Component({
selector: 'app-root',
template: `
<app-header [showLogo]="true" [title]="title" (logo)="logoClick.emit()">
<app-header
[showLogo]="true"
[title]="title"
(logo)="logoClick.emit()"
>
<ng-template #menu>
<ul>
<li><a [routerLink]="['/home']">Home</a></li>
Expand All @@ -32,15 +45,17 @@ class AppComponent {
@Component({
selector: 'app-header',
template: `
<a (click)="logo.emit()"><img src="assets/logo.png" *ngIf="showLogo" /></a>
<a (click)="logo.emit()">
<img src="assets/logo.png" *ngIf="showLogo" />
</a>
{{ title }}
<template [ngTemplateOutlet]="menu"></template>
`,
})
class AppHeaderComponent {
@Output() public readonly logo = new EventEmitter<void>();

@ContentChild('menu', staticFalse) public menu?: TemplateRef<ElementRef>;
@ContentChild('menu', staticFalse)
public menu?: TemplateRef<ElementRef>;
@Input() public showLogo = false;
@Input() public title = '';
}
Expand Down Expand Up @@ -107,7 +122,8 @@ describe('MAIN', () => {
});

it('asserts behavior of AppComponent', () => {
const logoClickSpy = jasmine.createSpy();
const logoClickSpy =
typeof jest === 'undefined' ? jasmine.createSpy() : jest.fn();
// in case of jest
// const logoClickSpy = jest.fn();

Expand Down Expand Up @@ -139,7 +155,9 @@ describe('MAIN', () => {
// Checking that AppComponents updates AppHeaderComponent.
fixture.componentInstance.title = 'Updated Application';
fixture.detectChanges();
expect(header.componentInstance.title).toBe('Updated Application');
expect(header.componentInstance.title).toBe(
'Updated Application',
);

// Checking that AppComponent listens on outputs of
// AppHeaderComponent.
Expand Down
Loading

0 comments on commit d0e5efc

Please sign in to comment.