Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(mock-render): throws on wrong usage #488 #518

Merged
merged 1 commit into from
May 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/articles/api/MockInstance.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ It helps to provide a predefined spy or value.

```ts
MockInstance(Service, 'methodName', () => 'fake');
MockInstance(Service, 'propName', 'fake');
MockInstance(Service, 'propName', () => 'fake', 'get');
MockInstance(Service, 'propName', () => undefined, 'set');
MockInstance(Component, 'propName', 'fake');
MockInstance(Directive, 'propName', () => 'fake', 'get');
MockInstance(Pipe, 'propName', () => undefined, 'set');
```

It returns the provided value, that allows to customize spies.
Expand Down
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
6 changes: 3 additions & 3 deletions libs/ng-mocks/src/lib/mock-instance/mock-instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ const mockInstanceMember = <T>(
};

/**
* @see https://ng-mocks.sudo.eu/api/ngMocks/stubMember
* @see https://ng-mocks.sudo.eu/api/MockInstance
*/
export function MockInstance<T extends object, K extends keyof T, S extends () => T[K]>(
instance: Type<T> | AbstractType<T>,
Expand All @@ -133,7 +133,7 @@ export function MockInstance<T extends object, K extends keyof T, S extends () =
): S;

/**
* @see https://ng-mocks.sudo.eu/api/ngMocks/stubMember
* @see https://ng-mocks.sudo.eu/api/MockInstance
*/
export function MockInstance<T extends object, K extends keyof T, S extends (value: T[K]) => void>(
instance: Type<T> | AbstractType<T>,
Expand All @@ -143,7 +143,7 @@ export function MockInstance<T extends object, K extends keyof T, S extends (val
): S;

/**
* @see https://ng-mocks.sudo.eu/api/ngMocks/stubMember
* @see https://ng-mocks.sudo.eu/api/MockInstance
*/
export function MockInstance<T extends object, K extends keyof T, S extends T[K]>(
instance: Type<T> | AbstractType<T>,
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