Sinon.JSでテストを書く

# 概要
テストコードを書くのに色々と苦労しました。
中でも苦労したのは、『クラスのコンストラクタ』と『メソッドのアクセス修飾子』です。
これらの書き方をまとめていきます。

# １．クラスのコンストラクタ
## インスタンス化
実際にインスタンス化して、できたインスタンスとの比較を行います。

```Class.ts
export default class Class {
  constructor(public index: number) {}
}
```

```test_Class.ts
import * as assert from 'assert';
import Class from './Class';
describe('Class', () => {
  describe('constructor', () => {
    it('normal', () => {
      const testCases = [ 1, 10, 0 ];
      for(const testCase of testCases) {
        const c = new Class(testCase);
        assert.deepEqual(c, { index: testCase }, 'Error in `'+String(testCase)+'`');
      }
    });
  });
});
```

## クラスのインスタンス化とメソッド実行の両方を行う
インスタンス化も、メソッドの実行も、スタブします。
その上で、実行回数・実行順序・それぞれの入力と出力を検証します。
あとは、コンストラクタとメソッドをそれぞれ検証するのみです。

```Class.ts
export default class Class {
  constructor(public index: number) {}

  getClass(){
    return 'Class';
  }
}
```

```index.ts
import Class from './Class';
export default function main() {
  const c = new Class(1);
  return c.getClass();
}
```

```test_index.ts
import { fake, stub, SinonStub } from 'sinon';
import * as assert from 'assert';
import main from './index';
import * as Class from './Class';
describe('main', () => {
  let stubClassConstructor!: SinonStub;
  let stubClassGet!: SinonStub;
  const dummyClass = new Class.default(10);
  const dummyString = 'stub';
  before(() => {
    stubClassConstructor = stub(Class, 'default');
    stubClassGet = stub(dummyClass, 'getClass');
  });
  beforeEach(() => {
    stubClassConstructor.reset();
    stubClassGet.reset();
    stubClassConstructor.callsFake(fake()).returns(dummyClass);
    stubClassGet.callsFake(fake()).returns(dummyString);
  });
  after(() => {
    stubClassConstructor.restore();
    stubClassGet.restore();
  });
  it('normal', () => {
    const result = case1();
    assert.equal(result, dummyString);
    assert.ok(stubClassConstructor.calledOnce);
    assert.ok(stubClassGet.calledOnce);
    assert.ok(stubClassGet.calledAfter(stubClassConstructor));
    assert.deepEqual(stubClassConstructor.firstCall.args, [ 1 ]);
    assert.deepEqual(stubClassGet.firstCall.args, []);
  });
});
```

## 継承したクラスのインスタンス化
継承元のコンストラクタは、[Object.setPrototypeOf](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf)を使うことでスタブできます。
このことを活用して、fake関数でスタブし、実行回数や入力を検証します。

```Class.ts
export default class Class {
  constructor(public index: number) {}
}
```

```Sub.ts
import Class from './Class';
export default class Sub extends Class {
  constructor() {
    super(5);
  }
}
```

```test_Sub.ts
import { fake } from 'sinon';
import * as assert from 'assert';
import Sub from './Sub';
describe('Sub', () => {
  const fakeOriginConstructor = fake();
  before(() => {
    Object.setPrototypeOf(Sub, fakeOriginConstructor);
  });
  beforeEach(() => {
    fakeOriginConstructor.resetHistory();
  });
  after(() => {
    delete require.cache[require.resolve('./Sub.ts')];
  });
  it('normal', () => {
    const s = new Sub();
    assert.deepEqual(s, {});
    assert.ok(fakeOriginConstructor.calledOnce);
    assert.deepEqual(fakeOriginConstructor.firstCall.args, [ 5 ]);
  });
});
```

# ２．メソッドのアクセス修飾子
## `private` や `protected` を修飾しているメソッド
`.` ではなく、 `['']` の形で実行できます。

```Class.ts
export default class Class {
  private getPrivate() {
    return 'private';
  }

  private getProtected() {
    return 'protected';
  }
}
```

```test_Class.ts
import { fake, stub, SinonStub } from 'sinon';
import * as assert from 'assert';
import Class from './Class';
describe('private', () => {
  it('normal', () => {
    const c = new Class();
    const result = c['getPrivate']();
    assert.equal(result, 'private');
  });
});
describe('protected', () => {
  it('normal', () => {
    const c = new Class();
    const result = c['getProtected']();
    assert.equal(result, 'protected');
  });
});
```

## privateとprotectedを修飾しているメソッドを使用しているメソッド
stub関数の中で、 `stub(Class.prototype, 'method' as any)` のように指定をします。
これでメソッドmethodの型を `any` としてTypeScriptは見るようになるので、静的型付けでエラーにならなくなります。
この `{object} as (type)` という使い方（ **Type Assertion** ）は、その場で型の認識を変えることができるので、テストコードとしては便利です。
詳しくは、 [Type Assertion - TypeScript Deep Dive](https://typescript-jp.gitbook.io/deep-dive/type-system/type-assertion) をご覧ください。

```Class.ts
export default class Class {
  get() {
    const modifier = {
      private: this.getPrivate(),
      protected: this.getProtected(),
    };
    return modifier;
  }

  private getPrivate() {
    return 'private';
  }

  private getProtected() {
    return 'protected';
  }
}
```

```test_Class.ts
import { fake, stub, SinonStub } from 'sinon';
import * as assert from 'assert';
import Class from './Class';
describe('get', () => {
  let c!: Class;
  let stubGetPrivate!: SinonStub;
  let stubGetProtected!: SinonStub;
  beforeEach(() => {
    c = new Class(10);
  });
  before(() => {
    stubGetPrivate = stub(Class.prototype, 'getPrivate' as any);
    stubGetProtected = stub(Class.prototype, 'getProtected' as any);
  });
  beforeEach(() => {
    stubGetPrivate.reset();
    stubGetProtected.reset();
    stubGetPrivate.callsFake(fake()).returns('dummy private');
    stubGetProtected.callsFake(fake()).returns('dummy protected');
  });
  after(() => {
    stubGetPrivate.restore();
    stubGetProtected.restore();
  });
  it('normal', () => {
    const result = c.get();
    assert.deepEqual(result, { private: 'dummy private', protected: 'dummy protected' });
    assert.ok(stubGetPrivate.calledOnce);
    assert.ok(stubGetProtected.calledOnce);
    assert.deepEqual(stubGetPrivate.firstCall.args, []);
    assert.deepEqual(stubGetProtected.firstCall.args, []);
  });
});
```