Skip to content

Commit

Permalink
feat: mock-builder + lots of helpers
Browse files Browse the repository at this point in the history
closes #44
  • Loading branch information
satanTime committed Jun 6, 2020
1 parent 94fde28 commit 5701dd8
Show file tree
Hide file tree
Showing 173 changed files with 6,944 additions and 430 deletions.
253 changes: 244 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,33 @@ Tested on:
Sure, you could flip a flag on schema errors to make your component dependencies not matter.
Or you could use this to mock them out and have the ability to assert on their inputs or emit on an output to assert on a side effect.

For an easy start check the [MockBuilder](#mockbuilder) first.

- [jasmine examples](https://github.com/ike18t/ng-mocks/tree/master/examples-jasmine)
- [jest examples](https://github.com/ike18t/ng-mocks/tree/master/examples-jest)

* [jasmine e2e tests](https://github.com/ike18t/ng-mocks/tree/master/tests-jasmine)
* [jest e2e tests](https://github.com/ike18t/ng-mocks/tree/master/tests-jest)

### Sections:

- [MockModule](#mockmodule)
- [MockComponent](#mockcomponents)
- [MockDirective](#mockdirectives)
- [MockPipe](#mockpipes)
- [MockDeclaration](#mockdeclarations)

* [MockBuilder](#mockbuilder) - facilitate creation of a mocked environment
* [MockRender](#mockrender) - facilitate render of components
* [MockHelper](#mockhelper) - facilitate extraction of directives of an element

- [Reactive Forms Components](#mocked-reactive-forms-components)
- [Structural Components](#usage-example-of-structural-directives)
- [Auto Spy](#auto-spy)
- [More examples](#other-examples-of-tests)

---

## MockComponent(s)

- Mocked component with the same selector
Expand All @@ -37,7 +58,8 @@ Or you could use this to mock them out and have the ability to assert on their i
- \_\_simulateTouch - calls `onTouched` on the mocked component bound to a FormControl
- exportAs support

### Usage Example
<details><summary>Click to see <strong>a usage example</strong></summary>
<p>

```typescript
import { ComponentFixture, TestBed } from '@angular/core/testing';
Expand Down Expand Up @@ -113,7 +135,7 @@ describe('MockComponent', () => {
</dependency-component-selector>
`);

// injected ng-content says as it was.
// injected ng-content stays as it was.
const mockedNgContent = localFixture.point.nativeElement.innerHTML;
expect(mockedNgContent).toContain('<p>inside content</p>');

Expand All @@ -128,14 +150,20 @@ describe('MockComponent', () => {
});
```

</p>
</details>

---

## MockDirective(s)

- Mocked directive with the same selector
- Inputs and Outputs with alias support
- Each directive instance has its own EventEmitter instances for outputs
- exportAs support

### Usage Example of Attribute Directives
<details><summary>Click to see <strong>a usage example</strong> of Attribute Directives</summary>
<p>

```typescript
import { ComponentFixture, TestBed } from '@angular/core/testing';
Expand Down Expand Up @@ -185,9 +213,13 @@ describe('MockDirective', () => {
});
```

### Usage Example of Structural Directives
</p>
</details>

It's important to render a structural directive first with right context,
<details><summary>Click to see <strong>a usage example</strong> of Structural Directives</summary>
<p>

It's important to render a structural directive first with the right context,
when assertions should be done on its nested elements.

```typescript
Expand Down Expand Up @@ -237,6 +269,11 @@ describe('MockDirective', () => {
});
```

</p>
</details>

---

## MockPipe(s)

- Mocked pipe with the same name.
Expand All @@ -245,7 +282,8 @@ describe('MockDirective', () => {

Personally, I found the best thing to do for assertions is to override the transform to write the args so that I can assert on the arguments.

### Usage Example
<details><summary>Click to see <strong>a usage example</strong></summary>
<p>

```typescript
import { ComponentFixture, TestBed } from '@angular/core/testing';
Expand Down Expand Up @@ -280,13 +318,19 @@ describe('MockPipe', () => {
});
```

</p>
</details>

---

## Mocked Reactive Forms Components

- Set value on the formControl by calling \_\_simulateChange
- Set touched on the formControl by calling \_\_simulateTouch
- Use the `MockedComponent` type to stay typesafe: `MockedComponent<YourReactiveFormComponent>`

### Usage Example
<details><summary>Click to see <strong>a usage example</strong></summary>
<p>

```typescript
import { ComponentFixture, TestBed } from '@angular/core/testing';
Expand Down Expand Up @@ -327,10 +371,17 @@ describe('MockReactiveForms', () => {
});
```

</p>
</details>

---

## MockDeclaration(s)

It figures out if it is a component, directive, or pipe and mocks it for you

---

## MockModule

- Mocks all components, directives, and pipes using MockDeclaration
Expand All @@ -339,7 +390,8 @@ It figures out if it is a component, directive, or pipe and mocks it for you

For providers I typically will use TestBed.get(SomeProvider) and extend it using a library like [ts-mocks](https://www.npmjs.com/package/ts-mocks).

### Usage Example
<details><summary>Click to see <strong>a usage example</strong></summary>
<p>

```typescript
import { ComponentFixture, TestBed } from '@angular/core/testing';
Expand Down Expand Up @@ -369,6 +421,179 @@ describe('MockModule', () => {
});
```

</p>
</details>

---

## MockBuilder

The simplest way to mock everything, but not the component for testing is usage of `MockBuilder`.
Check `examples/MockBuilder/` for real examples. It's useful together with [MockRender](#MockRender).

<details><summary>Click to see <strong>a usage example</strong></summary>
<p>

```typescript
import { TestBed } from '@angular/core/testing';
import { MockBuilder, MockRender } from 'ng-mocks';

describe('MockBuilder:simple', () => {
beforeEach(async () => {
const ngModule = MockBuilder(MyComponent, MyModule)
// mocking configuration here
.build();
// now ngModule is
// {
// imports: [MockModule(MyModule)], // but MyComponent wasn't mocked for the testing purposes.
// }
// and we can simply pass it to the TestBed.
return TestBed.configureTestingModule(ngModule).compileComponents();
});

it('should render content ignoring all dependencies', () => {
const fixture = MockRender(MyComponent);
expect(fixture).toBeDefined();
expect(fixture.debugElement.nativeElement.innerHTML).toContain('<div>My Content</div>');
});
});
```

</p>
</details>

<details><summary>Click to see <strong>a detailed information</strong></summary>
<p>

```typescript
import { MockBuilder } from 'ng-mocks';

// Mocks everything in MyModule (imports, declarations, providers)
// but keeps MyComponent as it is.
const ngModule = MockBuilder(MyComponent, MyModule).build();

// The same as code above.
const ngModule = MockBuilder().keep(MyComponent, { export: true }).mock(MyModule).build();

// If we want to keep a module, component, directive, pipe or provider as it is (not mocking).
// We should use .keep.
const ngModule = MockBuilder(MyComponent, MyModule)
.keep(SomeModule)
.keep(SomeComponent)
.keep(SomeDirective)
.keep(SomePipe)
.keep(SomeDependency)
.keep(SomeInjectionToken)
.build();
// If we want to mock something, even a part of a kept module we should use .mock.
const ngModule = MockBuilder(MyComponent, MyModule)
.mock(SomeModule)
.mock(SomeComponent)
.mock(SomeDirective)
.mock(SomePipe)
.mock(SomeDependency)
.mock(SomeInjectionToken)
.build();
// If we want to replace something with something we should use .replace.
// The replacement has to be decorated with the same decorator as the source.
// It's impossible to replace a provider or a service, we should use .provide or .mock for that.
const ngModule = MockBuilder(MyComponent, MyModule)
.replace(HttpClientModule, HttpClientTestingModule)
.replace(SomeComponent, SomeOtherComponent)
.replace(SomeDirective, SomeOtherDirective)
.replace(SomePipe, SomeOtherPipe)
.build();
// For pipes we can set its handler as the 2nd parameter of .mock too.
const ngModule = MockBuilder(MyComponent, MyModule)
.mock(SomePipe, value => 'My Custom Content')
.build();
// If we want to add or replace a provider or a service we should use .provide.
// It has the same interface as a regular provider.
const ngModule = MockBuilder(MyComponent, MyModule)
.provide(MyService)
.provide([SomeService1, SomeService2])
.provide({ provide: SomeComponent3, useValue: anything1 })
.provide({ provide: SOME_TOKEN, useFactory: () => anything2 })
.build();
// If we need to mock, or to use useValue we can use .mock for that.
const ngModule = MockBuilder(MyComponent, MyModule)
.mock(MyService)
.mock(SomeService1)
.mock(SomeService2)
.mock(SomeComponent3, anything1)
.mock(SOME_TOKEN, anything2)
.build();
// Anytime we can change our decision.
// The last action on the same object wins.
const ngModule = MockBuilder(MyComponent, MyModule)
.keep(SomeModule)
.mock(SomeModule)
.keep(SomeModule)
.mock(SomeModule)
.build();
// If we want to test a component, directive or pipe which wasn't exported
// we should mark it as an 'export'.
// Doesn't matter how deep it is. It will be exported to the level of TestingModule.
const ngModule = MockBuilder(MyComponent, MyModule)
.keep(SomeModuleComponentDirectivePipeProvider1, {
export: true,
})
.build();
// By default all definitions (kept and mocked) are added to the TestingModule
// if they are not dependency of another definition.
// Modules are added as imports to the TestingModule.
// Components, Directive, Pipes are added as declarations to the TestingModule.
// Providers and Services are added as providers to the TestingModule.
// If we don't want something to be added to the TestingModule at all
// we should mark it as a 'dependency'.
const ngModule = MockBuilder(MyComponent, MyModule)
.keep(SomeModuleComponentDirectivePipeProvider1, {
dependency: true,
})
.mock(SomeModuleComponentDirectivePipeProvider1, {
dependency: true,
})
.replace(SomeModuleComponentDirectivePipeProvider1, anything1, {
dependency: true,
})
.build();
// Imagine we want to render a structural directive by default.
// Now we can do that via adding a 'render' flag in its config.
const ngModule = MockBuilder(MyComponent, MyModule)
.mock(MyDirective, {
render: true,
})
.build();
// Imagine the directive has own context and variables.
// Then instead of flag we can set its context.
const ngModule = MockBuilder(MyComponent, MyModule)
.mock(MyDirective, {
render: {
$implicit: something1,
variables: { something2: something3 },
},
})
.build();
// If we use ContentChild in a component and we want to render it by default too
// we should use its id for that in the same way as for a mocked directive.
const ngModule = MockBuilder(MyComponent, MyModule)
.mock(MyDirective, {
render: {
blockId: true,
blockWithContext: {
$implicit: something1,
variables: { something2: something3 },
},
},
})
.build();
```

</p>
</details>

---

## MockRender

Provides a simple way to render anything for ease of testing directives, pipes, `@Inputs`, `@Outputs`, `@ContentChild` of a component, etc.
Expand All @@ -386,7 +611,8 @@ It is useful if you want to mock system tokens / services such as `APP_INITIALIZ

And don't forget to call `fixture.detectChanges()` and / or `await fixture.whenStable()` to trigger updates.

### Usage Example
<details><summary>Click to see <strong>a usage example</strong></summary>
<p>

```typescript
import { TestBed } from '@angular/core/testing';
Expand Down Expand Up @@ -455,6 +681,11 @@ describe('MockRender', () => {
});
```

</p>
</details>

---

## ngMocks

ngMocks provides functions to get attribute and structural directives from an element, find components and mock objects.
Expand All @@ -473,6 +704,8 @@ ngMocks provides functions to get attribute and structural directives from an el
* ngMocks.stub(service, methods)
* ngMocks.stub(service, property, 'get' | 'set')

- ngMocks.flushTestBed()

```typescript
// returns attribute or structural directive
// which belongs to current element.
Expand Down Expand Up @@ -580,6 +813,8 @@ In case of jest add it to `src/setupJest.ts`.
import 'ng-mocks/dist/jest';
```

---

## Find an issue or have a request?

Report it as an issue or submit a PR. I'm open to contributions.
Expand Down
6 changes: 3 additions & 3 deletions build-with-supported-angluars.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ do

echo $NEW

npm uninstall --no-save $OLD
npm install --no-save $NEW
npm uninstall --no-save $OLD --silent --quiet --no-progress
npm install --no-save $NEW --silent --quiet --no-progress
npm run build:all
done
npm install
npm install --silent --quiet --no-progress
npm run e2e
echo Testing complete
Loading

0 comments on commit 5701dd8

Please sign in to comment.