npm package: @hastom/testing
Description: Библиотека для помощи в написании тестов на node.js.
Latest version: 1.0.0

# testing

Библиотека для помощи в написании тестов на node.js. 

Экспортирует несколько хеплеров:

[[_TOC_]]

## defaultConfig

Дефолтный конфиг для юнит и интеграционных тестов. Старайтесь не добавлять в него проект-специфичные опции.

Использование: в файл `jest.config.js` напишите код:

```javascript
const { createDefaultConfig } = require('@atmruback/testing');

module.exports = {
    ...createDefaultConfig(),
};
```

Если проект находится на стадии поднятия test coverage, то стоит зафиксировать coverageThreshold на текущем уровне, чтобы не происходила деградация.
Параметр coverageThreshold можно установить как для global, так и для отдельных директорий. Пример:

```javascript
const { createDefaultConfig } = require('@atmruback/testing');

module.exports = {
    ...createDefaultConfig(),
    coverageThreshold: {
        global: {
            statements: 30,
        },
        './src/work-days/': {
            statements: 40,
        }
    }
};
```

Подробнее: https://jestjs.io/docs/configuration#coveragethreshold-object

Для интеграционных тестов в файле  `src/utils/test-utils/setup-integrations-db.ts` напишите код:
```typescript
import { initializeDatabase } from '@atmruback/testing/int/init-db';

import { dataSourceOptions } from '../../datasource';

export default async (): Promise<void> => initializeDatabase(dataSourceOptions);
```
`@atmruback/testing/int/mock-rmq` - мокает взаимодействие с RabbitMQ.

`@atmruback/testing/int/init-db` - создаёт базу данных под каждый воркер jest. Это необходимо, чтобы тесты не мешали друг другу при параллельном запуске.
Количество воркеров определяется переменной окружения `JEST_MAX_WORKERS`.

После этого в файле интеграционного теста `*.int.spec.ts` вам нужно будет указать для typeorm название базы данных под текущий воркер.
Пример:

```typescript
import { workerDatabaseName } from '@atmruback/testing/int/init-db';
//...
const moduleRef = await Test.createTestingModule({
    imports: [
        TypeOrmModule.forRoot(typeOrmModuleConfig({ database: workerDatabaseName })),
    ],
})
```

Для запуска всех тестов (интеграционных и юнит) используем команду:
```bash
DOTENV_CONFIG_PATH=.env.test node -r dotenv/config ./node_modules/.bin/jest --force-exit --coverage
```
Для запуска только юнит-тестов используем команду:
```bash
DOTENV_CONFIG_PATH=.env.test node -r dotenv/config ./node_modules/.bin/jest --force-exit --coverage --selectProjects unit
```
Для запуска только интеграционных тестов используем команду:
```bash
DOTENV_CONFIG_PATH=.env.test node -r dotenv/config ./node_modules/.bin/jest --force-exit --coverage --selectProjects int
```

Если энвы для данного проекта не применимы (библиотеки) то из команд запуска убираем подключение dotenv:
```bash
node ./node_modules/.bin/jest --force-exit --coverage
```
или
```bash
jest --force-exit --coverage
```

## provideServiceDefaultMock
## provideRepositoryDefaultMock

Создают моки сервиса/репозитория для использования в зависимостях для тестирования nest сервиса. Возвращают объект типа MockProxy<>. См. ниже "Библиотека `jest-mock-extended`".

`provideRepositoryDefaultMock` вынесена в файл `@atmruback/testing/repository-mock-factory`, чтобы не требовать от потребителей подключения `@nestjs/typeorm`.

Пример использования:

```typescript
import { LoggerService } from '@atmruback/logger';
import { MockProxy, provideServiceDefaultMock } from '@atmruback/testing';
import { provideRepositoryDefaultMock } from '@atmruback/testing/repository-mock-factory';
import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Repository } from 'typeorm';

import { MyService } from './my.service';

describe('myService', () => {
    let service: MyService;
    let myRepository: MockProxy<Repository<MyEntity>>;
    let loggerService: MockProxy<LoggerService>;

    beforeEach(async () => {
        const module: TestingModule = await Test.createTestingModule({
            providers: [MyService, provideRepositoryDefaultMock(MyEntity), provideServiceDefaultMock(LoggerService)],
        }).compile();

        service = module.get<MyService>(MyService);
        myRepository = module.get(getRepositoryToken(MyEntity));
        loggerService = module.get(LoggerService)
    });

    it('should do action', async () => {
        const criticalMock = loggerService.critical.calledWith('test').mockReturnValue('stub');
        const saveMock = myRepository.save.mockResolvedValue(<MyEntity>{});

        await service.doAction();

        expect(criticalMock).toHaveBeenCalledOnce();
        expect(saveMock).toHaveBeenCalledOnceWith({ testField: "testValue" });
    });
```

## guardMock
## createJwtGuardMock
## interceptorMock

Моки гардов и интерцепторов для интеграционных тестов. createJwtGuardMock создаёт новый мок jwt гарда с проставлением payload'а.
Пример:
```typescript
const moduleRef = await Test.createTestingModule({
    imports: [...],
})
    .overrideGuard(JwtGuard)
    .useValue(createJwtGuardMock({ id: testUserId, aud: TokenAudience.ACCOUNT }))
    .overrideGuard(MfaGuard)
    .useValue(guardMock)
    .overrideGuard(AuditLogMfaInterceptor)
    .useValue(interceptorMock)
    .compile();

```

## Библиотека `@total-typescript/shoehorn`

`@atmruback/testing` реэкспортирует методы из `@total-typescript/shoehorn`.
Решает проблему передачи в функции параметра, который удовлетворяет типу лишь частично. Ранее мы использовали для этого конструкцию `{} as MyType`.
Это не очень хорошо: мы привыкаем к нормальности `as`, который не является типобезопасным; и всегда нужно указывать конкретный тип.

Функции:
* `fromPartial` - позволяет передать в функцию параметр с неполным набором полей. Кинет ошибку, если тип параметра не соответствует ожидаемому. Заменяет  `{} as MyType`.
* `fromAny` - позволяет передать в функцию параметр любого типа. НЕ кинет ошибку, если тип параметра не соответствует ожидаемому. Заменяет `{} as unknown as MyType`.
* `fromExact` - заставляет указать весь набор полей для параметра. Полезно, при переключении между `fromPartial`/`fromAny` или когда хотите их удалить.

Пример использования:

```typescript
import { fromPartial } from "@atmruback/testing";

requiresRequest(
  fromPartial({
    body: {
      id: "123",
    },
  }),
);
```

Подробнее: https://www.npmjs.com/package/@total-typescript/shoehorn

## Библиотека `jest-extended`

`@atmruback/testing` реэкспортирует `jest-extended`.
Добавляет набор матчеров для jest: https://jest-extended.jestcommunity.dev/docs/matchers

Нужно импортировать '@atmruback/testing' и матчеры будут добавлены в глобальный скоуп.

Пример использования:

```typescript
import '@atmruback/testing';

describe('myService', () => {
    it('should do action', async () => {
        expect(null).toBeNil();
        expect(undefined).toBeNil();
        expect(true).not.toBeNil();
    });
```

Подробнее: https://www.npmjs.com/package/jest-extended

## Библиотека `jest-mock-extended`

`@atmruback/testing` реэкспортирует методы и типы из `jest-mock-extended`.
Библиотека для генерации типобезопасных моков для объектов и интерфейсов.

Пример:
```typescript
import { MockProxy, mock } from '@atmruback/testing';

class MyClass {
    public fooBar(text: string): number {
        return text.length;
    }
}

describe('test', () => {
    let myMock: MockProxy<MyClass>;

    beforeEach(() => {
        myMock = mock<MyClass>();
    });

    it('should do action', () => {
        myMock.fooBar.calledWith('foo').mockReturnValue(1);
        myMock.fooBar.calledWith('bar').mockReturnValue(2);

        expect(myMock.fooBar('foo')).toBe(1);
        expect(myMock.fooBar('bar')).toBe(2);
        expect(myMock.fooBar('foobar')).toBeUndefined();
    });
});

});
```

Подробнее: https://www.npmjs.com/package/jest-mock-extended