LoginSignup
11
11

More than 5 years have passed since last update.

標準出力のテストを書く

Posted at
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();
    });
  });
});

Screen Shot 2016-03-29 at 00.01.18.gif

単純な解決方法として、コンストラクタのオプション引数に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`);
  }
}

これで、コンストラクタの引数から標準出力の書き込みを受け取れるようになります。
関数が呼び出されたか?呼び出された時の引数はどのような値か?を知りたい時は、sinonspy()を利用します。

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で通せばテストは完成です。

Screen Shot 2016-03-29 at 00.13.59.gif

着色と脱色

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

11
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
11