Writing and Testing a stdin script with TypeScript
Tags: testing, javascript, typescript, jest
Published: 2022-05-31 02:59:39

Hello there, folks, how are you?

<img src="https://media.giphy.com/media/6SmS4j3yjTzeRSosRv/giphy.gif" />

One of my motivations for writing texts here in `dev.to` is to compile information which took me a lot of effort to find in one place.

So, one of these days I was writing a small application in TypeScript that used stdin from the terminal 

```shell
ts-node main.ts < input.txt
```

Everything went well until I had to test it. I spent more time than I'd like trying to find out how I would test this input. I tried a lot of different stuff that I saw on the internet but only one of them worked and this is the solution I'll present in this text.

<img src="https://media.giphy.com/media/FLznxjoxTdpCxtDmiu/giphy.gif" />

## Code Example

<img = src="https://media.giphy.com/media/l0Iy9d5y9XvH2qfvi/giphy.gif" />

First of all, an example of a script in typescript that receives stdin line by line and terminates when an empty line is entered:

```typescript
// 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))
}

```

You can check out the [readline docs](https://nodejs.org/api/readline.html)

## Preparing the project for testing

<img src="https://media.giphy.com/media/l0Iy3RS7F2jRheg48/giphy.gif" />

The question is: how would we test our `main` function that only calls our `someFunction`?

If we mock `readline`, we wouldn't be testing our app, we need to mock the stdin to get a realistic simulation of what our program is doing.

For that, we will use jest. In this particular project, these are my dependencies inside `package.json`:

```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",
    // ...
  },

```

The other config files:

```typescript
// 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"]
}

```

## Testing

<img src="https://media.giphy.com/media/3o7bu4ZLGQTSBRJIBi/giphy.gif" />

The package that does the trick is [mock-stdin](https://www.npmjs.com/package/mock-stdin) and is very simple to use:

```typescript
// 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()

```

Here is an example of a test for our `main` function:

```typescript
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

 }

```

That's it, folks, I hope this text helps you somehow!
Bye bye 

<img src="https://media.giphy.com/media/iFGvqsYOKrtT8gRzkG/giphy.gif" />

Use masks (yes!) and use emacs 
xoxo

```typescript
covidVaccines.forEach(takeYourShot)
```

<img src="https://media.giphy.com/media/3o7btMBGqquGTVJiF2/giphy.gif" />

<small> Cover photo: [Photo by Sigmund](https://unsplash.com/photos/elHKkgom1VU?utm_source=unsplash&utm_medium=referral&utm_content=creditShareLink) </small>