スタブ
スタブは、オブジェクトを置き換え、引数や戻り値を指定します。例えばAjaxでのサーバ呼び出しやDBなどの外部リソースや、複数の関数を呼び出しているような関数をテストする際に、呼び出し先の関数をスタブで上書きし、戻り値を個別に指定してあたかも外部リソースが呼ばれたかのようにテストをできます。スタブは前項で説明したスパイの関数も使用することができますが、スパイとの違いは関数に対してスタブを作成すると、オブジェクトが上書きされてしまうため、元の関数が呼び出されません。
リスト2-01はバリデーションのテストを行っています。Userクラスのvalidメソッドは、UserNameとMailAddressクラスのvalidメソッドを呼び出した組み合わせの結果を返しているため、それぞれの戻り値を偽装し、組み合わせのテストを行います。
describe("ユーザ情報のバリデーションのテスト", function() {
it("バリデーションのテスト", function() {
/** 事前準備 */
var userName = new UserName('Koike Naoki');
var mailAddress = new MailAddress('koike@koikenaoki.com');
var user = new User(userName, mailAddress);
// validメソッドをスタブにする ①
var userNameStub = sinon.stub(userName, 'valid');
var mailAddressStub = sinon.stub(mailAddress, 'valid');
// 1回目に呼び出された際の戻り値 ②
userNameStub.onFirstCall().returns(true);
mailAddressStub.onFirstCall().returns(true);
// 2回目に呼び出された際の戻り値
userNameStub.onSecondCall().returns(true);
mailAddressStub.onSecondCall().returns(false);
// 3回目に呼び出された際の戻り値
userNameStub.onThirdCall().returns(false);
mailAddressStub.onThirdCall().returns(true);
/** 実行 */
var actual1 = user.valid(userName, mailAddress);
var actual2 = user.valid(userName, mailAddress);
var actual3 = user.valid(userName, mailAddress);
/** 確認 */
expect(actual1).to.be.true;
expect(actual2).to.be.false;
expect(actual3).to.be.false;
});
});
function User(userName, mailAddress) {
this.userName = userName;
this.mailAddress = mailAddress;
}
User.prototype.valid = function() {
if (!this.userName.valid() || !this.mailAddress.valid()) {
return false;
}
return true;
};
①では、スタブオブジェクトを生成しています。UserNameとMailAddressクラスオブジェクトのvalidメソッドをスタブにしています。スタブオブジェクトの生成方法は、表2-01のとおりです。
| 呼び出し方法 | 概要 |
|---|---|
| var stub = sinon.stub(); |
空のスタブオブジェクトを生成。 主にコールバックで渡す無名関数などに対して使用する。 |
| var stub = sinon.stub(object, "method"); | 特定の関数に対してスタブオブジェクトを作成する。 |
| var stub = sinon.stub(object, "method", func); |
特定の関数に対してスタブオブジェクトを作成し、 指定した関数で処理を上書きします。 |
| var stub = sinon.stub(obj); | 対象オブジェクトのメソッドをすべてスタブにする。 |
②では、1回目~3回目まで呼び出された際の戻り値を指定しています。スタブはスパイオブジェクトが持っている関数は基本的に持っていますが、その他の主な関数は表2-02のとおりです。
| 呼び出し方法 | 概要 |
|---|---|
| stub.returns(obj); | 呼び出し時のリターン値を指定する。 |
| stub.throws(obj); | 呼び出し時に指定したExceptionを発生させる。 |
| stub.onCall(n); | n回目のスタブオブジェクトを返す。 |
| stub.onFirstCall(); | 1回目のスタブオブジェクトを返す。 |
| stub.onSecondCall(); | 2回目のスタブオブジェクトを返す。 |
モック
モックはスタブと混同しやすいのですが、最も違うのは、事前に予測しておくという点です。スタブは、あらかじめオブジェクトを置き換えておくだけで、それが呼び出されなくても関係ありません。モックの場合は、テスト対象の処理実行前に呼び出させる方法を指定しておき、処理実行後に指定通りに実行されたかチェックをします。そのとおりに実行されなければ、テストはエラーが発生したものとして扱われます。モックは、実行後に想定通りに呼ばれたかどうかチェックを行うため、個別のアサート文が必要になるような場合は、通常スタブを使用します。
リスト3-01は、バリデーションエラー時のテストです。メールアドレスのバリデーションが実行されるか確認し、登録関数が呼ばれないことを確認しています。
describe("ユーザ登録時のバリデーションのテスト", function() {
it("バリデーションエラー時に登録処理が呼ばれていないか", function() {
/** 事前準備 */
var userName = new UserName('Koike Naoki');
var mailAddress = new MailAddress('こいけ@koikenaoki.com');
var user = new User(userName, mailAddress);
// モックの準備 ①
var mailAddressMock = sinon.mock(mailAddress);
var userRepositoryMock = sinon.mock(UserRepository);
// 実行の予測 ②
// mailAddressのバリデーションは1回呼び出して
// ここで作成したオブジェクトで呼び出されている ③
mailAddressMock.expects("valid").once().on(mailAddress);
// バリデーションエラーなので登録処理は呼び出されない ④
userRepositoryMock.expects("register").never();
/** 実行 */
UserService.register(user, function() {
});
/** 確認 ⑤ */
expect(userRepositoryMock.verify()).to.be.true;
expect(mailAddressMock.verify()).to.be.true;
});
});
var UserService = {
register: function(user, callback) {
if (!user.valid()) {
return;
}
UserRepository.register(user, callback);
}
};
①はモックオブジェクトを作成し、②は実行の予測をしています。モックオブジェクトは表3-01のような機能が用意されています。実行内容を予測しテスト対象関数を実行後、⑤のようにverify関数を実行して、予測通りに実行されたかを評価します。
関数実行の予測は、expects関数に予測対象のメソッド名を指定し、取得したexpectationオブジェクトに対して関数を実行しながら指定していきます。expectationオブジェクトの持つ機能は表3-02のとおりです。
| 呼び出し方法 | 概要 |
|---|---|
| var mock = sinon.mock(obj); | モックオブジェクトを生成する。 |
| var expectation = mock.expects("method"); | 指定したmethodのexpectationオブジェクトを生成する。 |
| mock.restore(); | モックを削除する。 |
| mock.verify(); | モックをチェックする。 |
| 呼び出し方法 | 概要 |
|---|---|
| expectation.atLeast(number); | 最低呼び出し回数を指定する。 |
| expectation.atMost(number); | 最高呼び出し回数を指定する。 |
| expectation.never(); | 呼び出されないことを指定する。 |
| expectation.once(); | 1回呼び出されることを指定する。 |
| expectation.twice(); | 2回呼び出されることを指定する。 |
| expectation.exactly(number); | 引数のnumber回数呼び出されることを指定する。 |
| expectation.withArgs(arg1, arg2, ...); | 指定した引数で呼び出されることを指定する。 |
| expectation.on(obj); | 引数に指定したオブジェクトで関数が呼び出されることを指定する。 |
| expectation.verify(); | 指定した通りに呼び出されたかをチェックする。 |
