🗞
readlineを使ったNode.jsで簡易な標準入出力とモックのテスト
環境
- node.js 15.14.0
- npm 7.11.1
- typescript 4.2.4
- ts-node 9.1.1
- jest 26.6.3
- ts-jest 26.5.5
はじめに
node.jsで標準入力で処理対象を受け取って、加工した後に標準出力するプログラムを書くことがありました。
その際のテストを書くのに少しハマったので、ここで共有します。
簡単な標準入出力を扱うNode.jsプログラム
例えば以下のようなものを考えてみます。
空白で区切られた文字列を改行で区切られ、複数列が入力される。
文字列の空白の前をkey、後をvalueとしたコレクションを作成する。
ただ、既に出現しているkeyの場合は、そのvalueは無視する。(最初のvalueを結果とする)
最後にそのkey-valueのコレクションをjson文字列で標準出力に出す。
test.txt
hello world
foo baz
hoge fuga
hoge piyo
npx --silent ts-node index.ts < test.txt > result.json
result.json
{"hello":"world","foo":"baz","hoge":"fuga"}
標準入出力を使うと、このように簡単に入力を受け取ったり、出力をパイプを使ってjq
等の別のプログラムに渡すなどが容易なのがいいところですね。
Node.jsのreadlineモジュールを使って、簡単なプログラムを書くとこんな感じでしょうか。[1]
index.ts
import * as readline from "readline";
const r = readline.createInterface({
input: process.stdin,
terminal: false,
});
const result: { [key in string]: string } = {};
r.on("line", (line) => {
const elements = line.split(" ");
result[elements[0]] = result[elements[0]] ?? elements[1];
});
r.on("close", () => {
console.log(JSON.stringify(result));
});
テスト
プログラムを書いたら、テストを書くというのが世の常人の常というもの。
今回はこのように書きました。
環境で述べたとおり、テストフレームワークはjestです。
index.test.ts
import { sendLine, sendClose } from "./mock-stdin";
test("input lines has duplicate key", () => {
// given
const spyConsoleLog = jest.spyOn(console, "log");
// when
require("./index");
sendLine("hello world");
sendLine("foo baz");
sendLine("hoge fuga");
sendLine("hoge piyo");
sendClose();
// then
expect(spyConsoleLog).toBeCalledWith(
'{"hello":"world","foo":"baz","hoge":"fuga"}'
);
});
mock-stdin.ts
let sendLine: (line: string) => void;
let sendClose: () => void;
jest.mock("readline", () => {
return {
createInterface: () => {
return {
on: (event: string, callback: (...args: any) => void) => {
switch (event) {
case "line":
sendLine = callback;
break;
case "close":
sendClose = callback;
break;
}
},
};
},
};
});
export { sendLine, sendClose };
最後に
jestのmockは強力なので、こんなこともできちゃいます。
ただ、使いすぎると結局何をテストしているんだ、となることもあるのでご注意ください。[2]
リポジトリ
今回のサンプルプログラムのリポジトリはこちらです。
Discussion