Skip to content

Commit

Permalink
feat(mock-render): throws on wrong usage #488
Browse files Browse the repository at this point in the history
  • Loading branch information
satanTime committed May 8, 2021
1 parent 5dc9046 commit f4e3c4b
Show file tree
Hide file tree
Showing 15 changed files with 309 additions and 170 deletions.
51 changes: 45 additions & 6 deletions docs/articles/api/MockRender.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ sidebar_label: MockRender

## Important to know

### Returned type

:::caution
The `fixture` of `MockRender(Component)` is not assignable to
`ComponentFixture<Component>`.
Expand All @@ -31,7 +33,44 @@ It returns `MockedComponentFixture<T>` type. The difference is an additional `po
The best thing about it is that `fixture.point.componentInstance` is typed to the related class,
and **supports not only components, but also directives, services and tokens**.

### Example with a component
### One MockRender per one test

`MockRender` creates a special wrapper component which should be injected into `TestBed`.
The component is needed in order to render a custom template, which is provided or generated based on parameters.
An injection of a component into `TestBed` is possible only if `TestBed` has not been used yet.

Because of this,
usage of `MockRender` after usage of `TestBed.get`, `TestBed.inject`, `TestBed.createComponent` or another `MockRender`
triggers an error about dirty `TestBed`.

However, it is still possible to use `MockRender` more than once in a test.
It requires a reset of `TestBed` (check [`ngMocks.flushTestBed`](./ngMocks/flushTestBed.md)).
Please pay attention, that this makes all existing service instances obsolete.

```ts
it('two renders', () => {
MockRender('<div>1</div>'); // ok
MockRender('<div>2</div>'); // err
});

// The right way to use MockRender.
it('first of two renders', () => {
MockRender('<div>1</div>'); // ok
});
it('the second of two renders', () => {
MockRender('<div>2</div>'); // ok
});

// Possible, but not recommended.
it('two renders', () => {
MockRender('<div>1</div>'); // ok
ngMocks.flushTestBed();
MockRender('<div>2</div>'); // ok
MockRender('<div>3</div>', {}, {reset: true}); // ok
});
```

## Example with a component

```ts
const fixture = MockRender(AppComponent);
Expand All @@ -43,7 +82,7 @@ fixture.componentInstance;
fixture.point.componentInstance;
```

### Example with a directive
## Example with a directive

```ts
const fixture = MockRender(AppDirective);
Expand All @@ -55,7 +94,7 @@ fixture.componentInstance;
fixture.point.componentInstance;
```

### Example with a service
## Example with a service

```ts
const fixture = MockRender(TranslationService);
Expand All @@ -67,7 +106,7 @@ fixture.componentInstance;
fixture.point.componentInstance;
```

### Example with a token
## Example with a token

```ts
const fixture = MockRender(APP_BASE_HREF);
Expand All @@ -79,7 +118,7 @@ fixture.componentInstance;
fixture.point.componentInstance;
```

### Example with a custom template
## Example with a custom template

```ts
// custom template
Expand All @@ -97,7 +136,7 @@ fixture.componentInstance;
fixture.point.componentInstance;
```

### Example with providers
## Example with providers

If we want, we can specify providers for the render passing them via the 3rd parameter.
It is useful, when we want to **provide mock system tokens / services** such as `APP_INITIALIZER`, `DOCUMENT` etc.
Expand Down
150 changes: 72 additions & 78 deletions examples/MockBuilder/test.deep.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,91 +146,85 @@ describe('MockBuilder:deep', () => {
return TestBed.compileComponents();
});

it('should render', inject(
[HttpBackend],
(httpBackend: HttpBackend) => {
const fixture = MockRender(MyComponent);
expect(fixture).toBeDefined();
const content = fixture.nativeElement.innerHTML.replace(
/<!--.*?-->/gm,
'',
);
expect(content).toContain('<div>My Content</div>');
it('should render', () => {
const fixture = MockRender(MyComponent);
expect(fixture).toBeDefined();
const content = fixture.nativeElement.innerHTML.replace(
/<!--.*?-->/gm,
'',
);
expect(content).toContain('<div>My Content</div>');

expect(content).toContain(
'<div>MyComponent1: <c-1>If we need to tune testBed</c-1></div>',
);
expect(content).toContain(
'<div>MyComponent2: <c-2>More callbacks</c-2></div>',
);
expect(content).toContain(
'<div>MyComponent3: <c-3></c-3></div>',
);
expect(content).toContain(
'<div>KeepComponent: <c-keep>KeepComponent</c-keep></div>',
);
expect(content).toContain(
'<div>MockComponent: <c-mock></c-mock></div>',
);
expect(content).toContain(
'<div>ComponentStructural: -$implicit- b</div>',
);
expect(content).toContain(
'<div>MyComponent1: <c-1>If we need to tune testBed</c-1></div>',
);
expect(content).toContain(
'<div>MyComponent2: <c-2>More callbacks</c-2></div>',
);
expect(content).toContain('<div>MyComponent3: <c-3></c-3></div>');
expect(content).toContain(
'<div>KeepComponent: <c-keep>KeepComponent</c-keep></div>',
);
expect(content).toContain(
'<div>MockComponent: <c-mock></c-mock></div>',
);
expect(content).toContain(
'<div>ComponentStructural: -$implicit- b</div>',
);

expect(content).toContain(
'<div>MyDirective: <d-my></d-my></div>',
);
expect(content).toContain(
'<div>KeepDirective: <d-keep></d-keep></div>',
);
expect(content).toContain(
'MockDirective 1: <span>render b</span>',
);
expect(content).toContain('MockDirective 2: render $');
expect(content).toContain(
'<div>MyDirective: <d-my></d-my></div>',
);
expect(content).toContain(
'<div>KeepDirective: <d-keep></d-keep></div>',
);
expect(content).toContain(
'MockDirective 1: <span>render b</span>',
);
expect(content).toContain('MockDirective 2: render $');

expect(content).toContain('<div>MyPipe: MyPipe:text:0</div>');
expect(content).toContain(
'<div>KeepPipe: KeepPipe:text:0</div>',
);
expect(content).toContain('<div>MockPipe: </div>');
expect(content).toContain(
'<div>CustomizePipe: My Custom Result</div>',
);
expect(content).toContain(
'<div>RestorePipe: RestorePipe:text:0</div>',
);
expect(content).toContain('<div>MyPipe: MyPipe:text:0</div>');
expect(content).toContain('<div>KeepPipe: KeepPipe:text:0</div>');
expect(content).toContain('<div>MockPipe: </div>');
expect(content).toContain(
'<div>CustomizePipe: My Custom Result</div>',
);
expect(content).toContain(
'<div>RestorePipe: RestorePipe:text:0</div>',
);

expect(content).toContain('<div>TOKEN_KEEP: TOKEN_KEEP</div>');
expect(content).toContain('<div>TOKEN_MOCK: </div>');
expect(content).toContain(
'<div>TOKEN_CUSTOMIZE: My_Token</div>',
);
expect(content).toContain('<div>TOKEN_KEEP: TOKEN_KEEP</div>');
expect(content).toContain('<div>TOKEN_MOCK: </div>');
expect(content).toContain('<div>TOKEN_CUSTOMIZE: My_Token</div>');

expect(content).toContain(
'<div>AnythingKeep1: TheSameAsAnyProvider</div>',
);
expect(content).toContain(
'<div>AnythingKeep2: TheSameAsAnyProvider</div>',
);
expect(content).toContain(
'<div>myCustomProvider1: MyCustomProvider1</div>',
);
expect(content).toContain(
'<div>myCustomProvider2: MyCustomProvider2</div>',
);
expect(content).toContain(
'<div>myCustomProvider3: MyCustomProvider3</div>',
);
expect(content).toContain(
'<div>AnythingKeep1: TheSameAsAnyProvider</div>',
);
expect(content).toContain(
'<div>AnythingKeep2: TheSameAsAnyProvider</div>',
);
expect(content).toContain(
'<div>myCustomProvider1: MyCustomProvider1</div>',
);
expect(content).toContain(
'<div>myCustomProvider2: MyCustomProvider2</div>',
);
expect(content).toContain(
'<div>myCustomProvider3: MyCustomProvider3</div>',
);

expect(content).toContain('<div>myService1: </div>');
expect(content).toContain('<div>myService2: MyService2</div>');
expect(content).toContain(
'<div>serviceKeep: serviceKeep</div>',
);
expect(content).toContain(
'<div>serviceCustomize: My Customized String</div>',
);
expect(content).toContain('<div>serviceMock: </div>');
expect(content).toContain('<div>myService1: </div>');
expect(content).toContain('<div>myService2: MyService2</div>');
expect(content).toContain('<div>serviceKeep: serviceKeep</div>');
expect(content).toContain(
'<div>serviceCustomize: My Customized String</div>',
);
expect(content).toContain('<div>serviceMock: </div>');
});

it('replaces HttpBackend', inject(
[HttpBackend],
(httpBackend: HttpBackend) => {
// Checking that replacement works.
expect(httpBackend.constructor).toBeDefined();
expect(httpBackend.constructor.name).toEqual(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@ describe('MockComponent', () => {
});

it('ignores missed blocks', () => {
ngMocks.flushTestBed();
const loFixture = MockRender(TemplateOutletComponent);
const loComponent: any = loFixture.point.componentInstance;
if (isMockOf(loComponent, TemplateOutletComponent, 'c')) {
Expand Down
49 changes: 49 additions & 0 deletions libs/ng-mocks/src/lib/mock-render/func.reflect-template.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Component, Directive } from '@angular/core';
import { TestBed } from '@angular/core/testing';

import { extendClass } from '../common/core.helpers';
import coreReflectDirectiveResolve from '../common/core.reflect.directive-resolve';
import { AnyType } from '../common/core.types';
import { isNgDef } from '../common/func.is-ng-def';

const registerTemplateMiddleware = (template: AnyType<any>, meta: Directive): void => {
const child = extendClass(template);

let providers = meta.providers || [];
providers = [
...providers,
{
provide: template,
useExisting: child,
},
];
meta.providers = providers;

if (isNgDef(template, 'c')) {
Component(meta)(child);
} else {
Directive(meta)(child);
}
TestBed.configureTestingModule({
declarations: [child],
});
};

export default (template: AnyType<any>): Directive => {
if (!isNgDef(template, 'c') && !isNgDef(template, 'd')) {
return {};
}

const meta = { ...coreReflectDirectiveResolve(template) };

if (meta.selector && meta.selector.match(/[\[\],]/)) {
meta.selector = '';
}

if (!meta.selector) {
meta.selector = `ng-mocks-${template.name}`;
registerTemplateMiddleware(template, meta);
}

return meta;
};
Loading

0 comments on commit f4e3c4b

Please sign in to comment.