標準出力のテストを書く

```js: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`ライクなメソッドのテストを書きたい場合、このままテストを書こうとすると、テスト中の画面に表示されてしまいます。

```js: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](https://qiita-image-store.s3.amazonaws.com/0/28576/59fd8872-df30-2d4c-230a-0baaaa9c7f53.gif)

単純な解決方法として、コンストラクタのオプション引数に`process`を定義し、必要な時だけモックに差し替え可能にしました。

```js: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](http://sinonjs.org/docs/)の`spy()`を利用します。

```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');

      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](https://qiita-image-store.s3.amazonaws.com/0/28576/b3406deb-3f8b-f182-643d-38efdf82f1d2.gif)

着色と脱色
---
[chalk](https://github.com/chalk/chalk)などでスタイルを変更した文字列のテストは、[strip-ansi](https://github.com/chalk/strip-ansi)でスタイル変更前の文字列に戻せばテスト可能です。

```js: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`);
  }
}
```

```js: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プロパティなどで外部へ公開する必要があります。

```js: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`);
  }
}
```
```js: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