Написание и тестирование скрипта stdin с помощью TypeScript

Здравствуйте, друзья, как поживаете?

Одна из моих мотиваций писать тексты здесь, в dev.to — это собрать в одном месте информацию, на поиск которой у меня ушло много сил.

Итак, в один из этих дней я писал небольшое приложение на TypeScript, которое использовало stdin из терминала

ts-node main.ts < input.txt
Войти в полноэкранный режим Выйти из полноэкранного режима

Все шло хорошо, пока мне не пришлось его тестировать. Я потратил больше времени, чем хотелось бы, пытаясь понять, как мне протестировать этот ввод. Я перепробовал много разных способов, которые видел в интернете, но только один из них сработал, и именно это решение я представлю в этом тексте.

Пример кода

Прежде всего, пример скрипта на typescript, который принимает stdin строку за строкой и завершается, когда вводится пустая строка:

// main.ts
import * as readline from 'node:readline'
import { stdin as input, stdout as output } from 'node:process'

type RL = readline.Interface
type SomeFunction = (rl: RL) => (line: string) => void

const someFunction : SomeFunction = rl => line => {
  if (line === '') {
    rl.close()
  }

 /*
  * Do something with `line`
  */
  const result = // doSomething(line)

  console.log(result)
}


export const main = (): void => {
  const rl = readline.createInterface({ input, output })

  console.log("Please insert the data.")

  // reads line by line and calls someFunction(rl)(line)
  rl.on('line', someFunction(rl))
}

Войти в полноэкранный режим Выход из полноэкранного режима

Вы можете ознакомиться с документацией по readline

Подготовка проекта к тестированию

Возникает вопрос: как мы будем тестировать нашу функцию main, которая вызывает только нашу someFunction?

Если мы подражаем readline, мы не тестируем наше приложение, нам нужно подражать stdin, чтобы получить реалистичную симуляцию того, что делает наша программа.

Для этого мы будем использовать jest. В данном конкретном проекте это мои зависимости внутри package.json:

  "devDependencies": {
    "@babel/core": "^7.16.0",
    "@babel/preset-env": "^7.16.4",
    "@babel/preset-typescript": "^7.16.0",
    "@types/jest": "^27.0.3",
    "@types/node": "^16.3.1",
    "babel-jest": "^27.4.2",
    "jest": "^27.4.3",
    "mock-stdin": "^1.0.0",
    "typescript": "^4.3.5",
    // ...
  },

Войти в полноэкранный режим Выйти из полноэкранного режима

Другие файлы конфигурации:

// jest.config.ts
export default {
  clearMocks: true,
  testMatch: ['**/test/**/*.spec.ts'],
}


// babel.config.js
module.exports = {
  presets: [
    ['@babel/preset-env', {targets: {node: 'current'}}],
    '@babel/preset-typescript',
  ],
};

// tsconfig.json
{
  "compilerOptions": {
    "module": "commonjs",
    "moduleResolution": "node",
    "declaration": true,
    "removeComments": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "target": "ES2019",
    "lib": ["ES2019"],
    "sourceMap": true,
    "outDir": "./dist",
    "incremental": true,
    "esModuleInterop": true,
    "strict": true
  },
  "include": ["./src/**/*", "./test/**/*"],
  "exclude": ["node_modules"]
}

Войти в полноэкранный режим Выход из полноэкранного режима

Тестирование

Пакет, который выполняет этот трюк, называется mock-stdin и очень прост в использовании:

// 1. import the lib
import mockStdin from 'mock-stdin'

// 2. start it 
const stdin = mockStdin.stdin()

// 3. use it
stdin.send("some input")

// 4. end it
stdin.end()

Войти в полноэкранный режим Выход из полноэкранного режима

Вот пример теста для нашей функции main:

import mockStdin from 'mock-stdin'
import { main } from '../../src/main'

// mock console.log
console.log = jest.fn()

describe('main.ts', () => {
  let stdin: ReturnType<typeof mockStdin.stdin>

  // just a helper function to start the application
  // and mock the input
  const execute = (input: string): void => {
    main()
    stdin.send(input)
    stdin.end()
  }

  beforeEach(() => {
    stdin = mockStdin.stdin()
  })

  describe('when input is valid', () => {
    const input = // something
    const expectedResult = // another thing

    beforeEach(() => {
      execute(input)
    })

    it('should print the correct output', () => {
      expect(console.log).toBeCalledWith(expectedResult)
    })
  })

  // another describe blocks

 }

Вход в полноэкранный режим Выход из полноэкранного режима

Вот и все, друзья, надеюсь, этот текст вам хоть как-то поможет!
Пока-пока

Используйте маски (да!) и используйте emacs
xoxo

covidVaccines.forEach(takeYourShot)
Вход в полноэкранный режим Выход из полноэкранного режима

Фотография на обложке: Фото Sigmund

Оцените статью
Procodings.ru
Добавить комментарий