src/index.js
export default class Reporter {
/**
* @param {string[]} args - a output string
* @returns {undefined}
*/
report(...args) {
process.stdout.write(`${args.join(' ')}\n`);
}
}
例えば上記のように、引数をそのまま標準出力へ印字するconsole.log
ライクなメソッドのテストを書きたい場合、このままテストを書こうとすると、テスト中の画面に表示されてしまいます。
test/index.js
import Reporter from '../src';
import assert from 'power-assert';
describe('Reporter', () => {
describe('report', () => {
it('should be printed to the stdout a given argument as a string.', () => {
const repoter = new Reporter;
repoter.report('foo', 'bar', 'baz');
// assert();
});
});
});
単純な解決方法として、コンストラクタのオプション引数にprocess
を定義し、必要な時だけモックに差し替え可能にしました。
src/index.js
export default class Reporter {
/**
* @constructor
* @param {object} [options] - a repoter options
* @param {process} [options.process=process] - a process instance or mock
*/
constructor(options = {}) {
this.opts = Object.assign(
{ process },
options,
);
}
/**
* @param {string[]} args - a output string
* @returns {undefined}
*/
report(...args) {
this.opts.process.stdout.write(`${args.join(' ')}\n`);
}
}
これで、コンストラクタの引数から標準出力の書き込みを受け取れるようになります。
関数が呼び出されたか?呼び出された時の引数はどのような値か?を知りたい時は、sinonのspy()
を利用します。
import Reporter from '../src';
import sinon from 'sinon';
import assert from 'power-assert';
describe('Reporter', () => {
describe('report', () => {
it('should be printed to the stdout a given argument as a string.', () => {
const process = {
stdout: {
write: sinon.spy(),
},
};
const repoter = new Reporter({ process });
repoter.report('foo', 'bar', 'baz');
assert(process.stdout.write.args[0][0] === 'foo bar baz\n');
});
});
});
定義したprocess.stdout.write
の.args
から、呼び出した時のarguments
を得られるので、これをassert
で通せばテストは完成です。
着色と脱色
chalkなどでスタイルを変更した文字列のテストは、strip-ansiでスタイル変更前の文字列に戻せばテスト可能です。
src/index.js
import chalk from 'chalk';
export default class Reporter {
/**
* @constructor
* @param {object} [options] - a repoter options
* @param {process} [options.process=process] - a process instance or mock
*/
constructor(options = {}) {
this.opts = Object.assign(
{ process },
options,
);
}
/**
* @param {string[]} args - a output string
* @returns {undefined}
*/
report(...args) {
this.opts.process.stdout.write(`${args.map((arg) => {
return chalk.underline(arg);
}).join(' ')}\n`);
}
}
test/index.js
import Reporter from '../src';
import sinon from 'sinon';
import stripAnsi from 'strip-ansi';
import assert from 'power-assert';
describe('Reporter', () => {
describe('report', () => {
it('should be printed to the stdout a given argument as a string', () => {
const process = {
stdout: {
write: sinon.spy(),
},
};
const repoter = new Reporter({ process });
repoter.report('foo', 'bar', 'baz');
const firstOutput = stripAnsi(process.stdout.write.args[0][0]);
assert(firstOutput === 'foo bar baz\n');
});
});
});
逆に、「スタイルが適用されたか」のテストを書く場合は、テストしたいスタイルを共有出来るように、staticプロパティなどで外部へ公開する必要があります。
src/index.js
import chalk from 'chalk';
export default class Reporter {
/**
* @static
* @property em - a emphasis text style
*/
static em = chalk.underline;
/**
* @constructor
* @param {object} [options] - a repoter options
* @param {process} [options.process=process] - a process instance or mock
*/
constructor(options = {}) {
this.opts = Object.assign(
{ process },
options,
);
}
/**
* @param {string[]} args - a output string
* @returns {undefined}
*/
report(...args) {
this.opts.process.stdout.write(`${args.map((arg) => {
return this.constructor.em(arg);
}).join(' ')}\n`);
}
}
test/index.js
import Reporter from '../src';
import sinon from 'sinon';
import assert from 'power-assert';
describe('Reporter', () => {
describe('report', () => {
it('should be printed to the stdout a given argument as a string', () => {
const process = {
stdout: {
write: sinon.spy(),
},
};
const repoter = new Reporter({ process });
repoter.report('foo', 'bar', 'baz');
const firstOutput = process.stdout.write.args[0][0];
const expectedOutput = `${Reporter.em('foo')} ${Reporter.em('bar')} ${Reporter.em('baz')}\n`;
assert(firstOutput === expectedOutput);
});
});
});
環境
以下のレポジトリを使用します。
https://github.com/59798/node-howto-stdout-test