Skip to content

feat: add automatic mock for angular entities (#2908) #2953

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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
13 changes: 13 additions & 0 deletions e2e/automocks/__mocks__/test-manual-mock.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Component, input } from '@angular/core';

@Component({
selector: 'test',
template: 'this is a manual mock for test',
standalone: true,
styles: [':host { display: block; }'],
})
export class TestComponent {
public value = input.required<string>();

method() {}
}
34 changes: 34 additions & 0 deletions e2e/automocks/__tests__/test-automocks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Component } from '@angular/core';
import { TestBed } from '@angular/core/testing';

import * as testManualMockComponent from '../test-manual-mock.component';
import * as testModule from '../test.module';

jest.mock('../test.module');
jest.mock('../test-manual-mock.component');

it('should mock module', () => {
@Component({
template: `
<test [value]="'value'">{{ 'test' | test }}</test>
<div test [value]="'value'">{{ 'test' | test }}</div>
`,
imports: [testModule.TestModule],
})
class TestComponent {}

jest.spyOn(console, 'error');

expect(() => TestBed.createComponent(TestComponent).detectChanges()).not.toThrow();

expect(console.error).not.toHaveBeenCalled();
});

it('should ignore modules with manual mock', () => {
const fixture = TestBed.createComponent(testManualMockComponent.TestComponent);

fixture.detectChanges();

expect(fixture.debugElement.nativeElement.innerHTML).toBe('this is a manual mock for test');
expect(jest.isMockFunction(fixture.componentInstance.method)).toBeFalsy();
});
3 changes: 3 additions & 0 deletions e2e/automocks/external-lib/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './some.service';
export * from './some.component';
export * from './some.pipe';
11 changes: 11 additions & 0 deletions e2e/automocks/external-lib/some.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Component, input, output } from '@angular/core';

@Component({
selector: 'some',
template: ``,
})
export class SomeComponent {
public readonly value = input.required<string>();

public readonly helloWorld = output();
}
10 changes: 10 additions & 0 deletions e2e/automocks/external-lib/some.pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
name: 'some',
})
export class SomePipe implements PipeTransform {
public transform(value: string): string {
return value.toUpperCase();
}
}
8 changes: 8 additions & 0 deletions e2e/automocks/external-lib/some.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Injectable } from '@angular/core';

@Injectable({
providedIn: 'root',
})
export class SomeService {
public doSomething(): void {}
}
23 changes: 23 additions & 0 deletions e2e/automocks/jest-cjs.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { JestConfigWithTsJest } from 'ts-jest';

const config: JestConfigWithTsJest = {
displayName: 'e2e-automocks',
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/../setup-test-env.ts', '<rootDir>/setup-test-env.ts'],
transform: {
'^.+\\.(ts|mjs|js|html)$': [
'<rootDir>/../../build/index.js',
{
babelConfig: true,
tsconfig: '<rootDir>/tsconfig-cjs.spec.json',
stringifyContentPathRegex: '\\.(html|svg)$',
},
],
},
transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
moduleNameMapper: {
'^external-lib$': '<rootDir>/external-lib/index.ts',
},
};

export default config;
22 changes: 22 additions & 0 deletions e2e/automocks/jest-esm.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { JestConfigWithTsJest } from 'ts-jest';

const config: JestConfigWithTsJest = {
displayName: 'e2e-automocks',
extensionsToTreatAsEsm: ['.ts', '.mts'],
setupFilesAfterEnv: ['<rootDir>/../setup-test-env.mts', '<rootDir>/setup-test-env.mts'],
transform: {
'^.+\\.(ts|mts|mjs|js|html)$': [
'<rootDir>/../../build/index.js',
{
babelConfig: true,
useESM: true,
tsconfig: '<rootDir>/tsconfig-esm.spec.json',
},
],
},
moduleNameMapper: {
'^external-lib$': '<rootDir>/external-lib/index.ts',
},
};

export default config;
23 changes: 23 additions & 0 deletions e2e/automocks/jest-transpile-cjs.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { JestConfigWithTsJest } from 'ts-jest';

const config: JestConfigWithTsJest = {
displayName: 'e2e-automocks',
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/../setup-test-env.ts', '<rootDir>/setup-test-env.ts'],
transform: {
'^.+\\.(ts|mjs|js|html)$': [
'<rootDir>/../../build/index.js',
{
babelConfig: true,
tsconfig: '<rootDir>/tsconfig-transpile-cjs.spec.json',
stringifyContentPathRegex: '\\.(html|svg)$',
},
],
},
transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
moduleNameMapper: {
'^external-lib$': '<rootDir>/external-lib/index.ts',
},
};

export default config;
22 changes: 22 additions & 0 deletions e2e/automocks/jest-transpile-esm.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { JestConfigWithTsJest } from 'ts-jest';

const config: JestConfigWithTsJest = {
displayName: 'e2e-automocks',
extensionsToTreatAsEsm: ['.ts', '.mts'],
setupFilesAfterEnv: ['<rootDir>/../setup-test-env.mts', '<rootDir>/setup-test-env.mts'],
transform: {
'^.+\\.(ts|mts|mjs|js|html)$': [
'<rootDir>/../../build/index.js',
{
babelConfig: true,
useESM: true,
tsconfig: '<rootDir>/tsconfig-transpile-esm.spec.json',
},
],
},
moduleNameMapper: {
'^external-lib$': '<rootDir>/external-lib/index.ts',
},
};

export default config;
52 changes: 52 additions & 0 deletions e2e/automocks/mock-component-example/my.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { SomeComponent } from 'external-lib';

import { MyComponent } from './my.component';

jest.mock('external-lib');

describe('MyComponent', () => {
let fixture: ComponentFixture<MyComponent>;
let component: MyComponent;

beforeEach(() => {
fixture = TestBed.createComponent(MyComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

describe('if isVisible is true', () => {
beforeEach(() => {
fixture.componentRef.setInput('isVisible', true);
fixture.detectChanges();
});

it('should render SomeComponent', () => {
expect(fixture.debugElement.query(By.directive(SomeComponent))).toBeTruthy();
});

describe('on helloWorld event', () => {
beforeEach(() => {
jest.spyOn(component, 'onHelloWorld');

fixture.debugElement.query(By.directive(SomeComponent)).triggerEventHandler('helloWorld', 'hello');
});

it('should call onHelloWorld', () => {
expect(component.onHelloWorld).toHaveBeenCalledWith('hello');
});
});
});

describe('if isVisible is false', () => {
beforeEach(() => {
fixture.componentRef.setInput('isVisible', false);
fixture.detectChanges();
});

it('should not render SomeComponent', () => {
expect(fixture.debugElement.query(By.directive(SomeComponent))).toBeFalsy();
});
});
});
20 changes: 20 additions & 0 deletions e2e/automocks/mock-component-example/my.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Component, input, signal } from '@angular/core';
import { SomeComponent } from 'external-lib';

@Component({
selector: 'my',
template: `
@if (isVisible()) {
<some [value]="value()" (helloWorld)="onHelloWorld($event)" />
}
`,
imports: [SomeComponent],
})
export class MyComponent {
public readonly isVisible = input(false);
public readonly value = signal('');

public onHelloWorld(data: string): void {
console.log(data);
}
}
35 changes: 35 additions & 0 deletions e2e/automocks/mock-pipe-example/my.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SomePipe } from 'external-lib';

import { MyComponent } from './my.component';

jest.mock('external-lib');

describe('MyComponent', () => {
let fixture: ComponentFixture<MyComponent>;
let component: MyComponent;

beforeEach(() => {
jest.mocked(SomePipe).prototype.transform.mockReturnValue('transformed');

fixture = TestBed.createComponent(MyComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should render transformed value', () => {
expect(fixture.nativeElement.textContent).toBe('transformed');
});

describe('if value changes', () => {
beforeEach(() => {
component.value.set('new value');
jest.mocked(SomePipe).prototype.transform.mockReturnValue('new transformed');
fixture.detectChanges();
});

it('should render new transformed value', () => {
expect(fixture.nativeElement.textContent).toBe('new transformed');
});
});
});
11 changes: 11 additions & 0 deletions e2e/automocks/mock-pipe-example/my.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Component, signal } from '@angular/core';
import { SomePipe } from 'external-lib';

@Component({
selector: 'my',
template: ` <p>{{ value() | some }}</p> `,
imports: [SomePipe],
})
export class MyComponent {
public readonly value = signal('');
}
29 changes: 29 additions & 0 deletions e2e/automocks/mock-service-example/my.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { SomeService } from 'external-lib';

import { MyComponent } from './my.component';

jest.mock('external-lib');

describe('MyComponent', () => {
let fixture: ComponentFixture<MyComponent>;
let someService: jest.Mocked<SomeService>;

beforeEach(() => {
someService = jest.mocked(TestBed.inject(SomeService));

fixture = TestBed.createComponent(MyComponent);
fixture.detectChanges();
});

describe('on button click', () => {
beforeEach(() => {
fixture.debugElement.query(By.css('button')).triggerEventHandler('click', null);
});

it('should call doSomething on SomeService', () => {
expect(someService.doSomething).toHaveBeenCalled();
});
});
});
14 changes: 14 additions & 0 deletions e2e/automocks/mock-service-example/my.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Component, inject } from '@angular/core';
import { SomeService } from 'external-lib';

@Component({
selector: 'my',
template: `<button (click)="onButtonClick()">Click me</button>`,
})
export class MyComponent {
private readonly someService = inject(SomeService);

public onButtonClick(): void {
this.someService.doSomething();
}
}
3 changes: 3 additions & 0 deletions e2e/automocks/setup-test-env.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { setupAutoMocks } from '../../setup-env/automocks.mjs';

setupAutoMocks();
3 changes: 3 additions & 0 deletions e2e/automocks/setup-test-env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { setupAutoMocks } from '../../setup-env/automocks.js';

setupAutoMocks();
13 changes: 13 additions & 0 deletions e2e/automocks/test-manual-mock.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Component, input } from '@angular/core';

@Component({
selector: 'test',
template: 'this is a test',
standalone: true,
styles: [':host { display: block; }'],
})
export class TestComponent {
public value = input.required<string>();

method() {}
}
13 changes: 13 additions & 0 deletions e2e/automocks/test.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Component, input } from '@angular/core';

@Component({
selector: 'test',
template: 'this is a test',
standalone: true,
styles: [':host { display: block; }'],
})
export class TestComponent {
public value = input.required<string>();

method() {}
}
11 changes: 11 additions & 0 deletions e2e/automocks/test.directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Directive, input } from '@angular/core';

@Directive({
selector: '[test]',
standalone: true,
})
export class TestDirective {
public value = input.required<string>();

method() {}
}
Loading