よくある質問
- 一般
- Jasmine と連携するその他のソフトウェア
- スペックの記述
-
非同期テスト
- どの非同期スタイルを使用すべきですか?また、その理由は?
- 一部の非同期スペックの失敗が、スイートエラーや別のスペックの失敗として報告されるのはなぜですか?
- Jasmine がスペックを並列で実行するのを停止するにはどうすればよいですか?
- コールバックを受け取り、Promise を返す(または async 関数である)スペックを記述できないのはなぜですか?代わりに何をすべきですか?
- ただし、成功と失敗を異なるチャネルで通知するコードをテストする必要があります。変更することはできません(またはしたくありません)。どうすればよいですか?
- 非同期関数が
done
を複数回呼び出すことができないのはなぜですか?代わりに何をすべきですか? describe
に async 関数を渡すことができないのはなぜですか?非同期で読み込まれたデータからスペックを生成するにはどうすればよいですか?- 非同期でデータをフェッチした後に何かをレンダリングする UI コンポーネントのように、Promise やコールバックがない非同期の動作をテストするにはどうすればよいですか?
- テスト対象のコードが完了する前に発生する非同期コールバックに渡された引数についてアサートする必要があります。最適な方法は何ですか?
- 拒否された Promise が原因でスペックが失敗した場合、Jasmine が常にスタックトレースを表示するとは限らないのはなぜですか?
- 未処理の Promise の拒否エラーが発生していますが、誤検知だと思います。
- スパイ
- Jasmine への貢献
一般
Jasmine の次のリリースはいつですか?
それは貢献のペースとメンテナーの時間の都合によります。
Jasmine は完全にボランティアによる活動であり、リリースがいつになるかを予測することは難しく、タイムラインを約束することはできません。過去には、新機能を含むリリースは通常 1〜6 か月ごとに行われていました。新しいバグが見つかった場合は、できるだけ早く修正をリリースするように努めています。
Jasmine はどのようにバージョン管理されていますか?
Jasmine は、できる限り セマンティックバージョニング に従うように努めています。これは、破壊的な変更やその他の重要な作業のためにメジャーバージョン (1.0, 2.0 など) を予約することを意味します。ほとんどの Jasmine リリースは、マイナーリリース (2.3, 2.4 など) になります。メジャーリリースはまれです。
多くの人が、Node でスペックを実行する jasmine
パッケージ、または jasmine-browser-runner
パッケージのいずれかを介して Jasmine を使用しています。歴史的な理由から、これらのパッケージには異なるバージョン管理戦略があります。
jasmine
のメジャーバージョンとマイナーバージョンはjasmine-core
と一致するため、jasmine
の依存関係を更新すると、最新のjasmine-core
も取得できます。パッチバージョンは個別に処理されます。jasmine-core
のパッチリリースには、対応するjasmine
のパッチリリースは必要ありません。またはその逆も同様です。jasmine-browser-runner
のバージョン番号は、jasmine-core
のバージョン番号とは関係ありません。jasmine-core
をピア依存関係として宣言します。yarn
とnpm
は、互換性のあるバージョンのjasmine-core
を自動的にインストールするか、パッケージの直接依存関係として追加することでバージョンを指定できます。
Jasmine は一般的に、メジャーリリースを除いて、ブラウザまたは Node バージョンのサポートを終了することを避けます。この例外は、サポート終了を迎えた Node バージョン、ローカルにインストールできなくなった、または CI ビルドでテストできなくなったブラウザ、セキュリティアップデートを受けなくなったブラウザ、およびセキュリティアップデートを受けなくなったオペレーティングシステムでのみ実行されるブラウザです。これらの環境で Jasmine が動作するように合理的な努力をしますが、それらが壊れた場合に必ずしもメジャーリリースを行うとは限りません。
jasmine-browser-runner で外部 URL からスクリプトを使用するにはどうすればよいですか?
jasmine-browser.json
または jasmine-browser.js
ファイルの srcFiles
にスクリプトの URL を追加できます。
// ...
srcFiles: [
"https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.0/jquery.min.js",
"**/*.js"
],
// ...
Jasmine は ES モジュール内のコードをテストできますか?
はい。正確なプロセスは、Jasmine の使用方法によって異なります。
- スタンドアロンディストリビューション、または HTML タグを制御するその他のブラウザ内セットアップを使用している場合は、
<script type="module">
を使用します。 - jasmine NPM パッケージを使用している場合、スクリプトは 動的インポート を使用してロードされます。これは、ファイルが
package.json
に"type": "module"
があるパッケージ内にある場合、または名前が.mjs
で終わる場合、ES モジュールとして扱われることを意味します。 - jasmine-browser-runner は、名前が
.mjs
で終わる場合、スクリプトを ES モジュールとしてロードします。esmFilenameExtension
設定プロパティを使用して、これを上書きできます。 - Karma などのサードパーティツールを使用して Jasmine を実行している場合は、そのツールのドキュメントを確認してください。
Jasmine がスペック内で複数の期待値の失敗を許可するのはなぜですか?それを無効にするにはどうすればよいですか?
特定の結果をアサートするには、複数の期待値が必要になる場合があります。このような状況では、いずれかの期待値を合格させようとする前に、すべての期待値が失敗するのを確認すると役立つ場合があります。これは、単一のコード変更で複数の期待値が合格になる場合に特に役立ちます。
各スペックが最初の期待値の失敗で停止するようにする場合は、oneFailurePerSpec
オプションを true
に設定できます。
- スタンドアロンディストリビューションを使用している場合は、「オプション」をクリックし、次に「期待値の失敗でスペックを停止」をクリックするか、
boot.js
を編集してオプションを永続的に設定します。 jasmine
NPM パッケージを使用している場合は、設定ファイル (通常はspec/support/jasmine.json
) でstopSpecOnExpectationFailure
をtrue
に設定します。- jasmine-core をラップするサードパーティツールを使用している場合は、構成オプションを渡す方法について、そのツールのドキュメントを確認してください。
- jasmine-core を直接使用している場合は、Env#configure に渡すオブジェクトに追加します。
スペックに関連付けられた afterEach 関数または afterAll 関数は引き続き実行されることに注意してください。
アサーションがないスペックを Jasmine で失敗させるにはどうすればよいですか?
デフォルトでは、Jasmine はスペックに期待値が含まれている必要はありません。failSpecWithNoExpectations
オプションを true
に設定することで、その動作を有効にできます。
- スタンドアロンディストリビューションを使用している場合は、
lib/jasmine-<VERSION>/boot.js
のconfig
オブジェクトに追加します。 jasmine
NPM パッケージを使用している場合は、設定ファイル (通常はspec/support/jasmine.json
) に追加します。- jasmine-core をラップするサードパーティツールを使用している場合は、構成オプションを渡す方法について、そのツールのドキュメントを確認してください。
- jasmine-core を直接使用している場合は、Env#configure に渡すオブジェクトに追加します。
failSpecWithNoExpectations
オプションに依存することはお勧めしません。これは、各スペックに少なくとも 1 つの期待値があることを保証するだけであり、検証しようとしている動作が機能しない場合にスペックが実際に正しい理由で失敗することを保証するものではありません。スペックが実際に正しいことを確認する唯一の方法は、両方の方法を試してみて、テスト対象のコードが機能している場合は合格し、テスト対象のコードが壊れている場合は意図した方法で失敗することを確認することです。ごく少数の人は、テストせずに常に適切なスペックを作成できます。これは、ごく少数の人がテストせずに常に動作するテスト以外のコードを配信できることと同じです。
TypeScript プロジェクトで Jasmine を使用するにはどうすればよいですか?
Jasmine と TypeScript を一緒に使用する方法は 2 つあります。
1 つ目は、@babel/register
を使用して、インポート時に TypeScript ファイルを JavaScript にその場でコンパイルすることです。例については、Jasmine NPM を使用した React アプリのテストを参照してください。このアプローチは、セットアップが簡単で、可能な限り最速の編集-コンパイル-実行スペックサイクルを提供しますが、デフォルトでは型チェックは提供されません。スペック用に noEmit
を true
に設定した別の TypeScript 設定ファイルを作成し、スペックを実行する前または後に tsc
を実行することで、型チェックを追加できます。
2 つ目のアプローチは、TypeScript スペックファイルをディスク上の JavaScript ファイルにコンパイルし、コンパイルされた TypeScript ファイルを実行するように Jasmine を構成することです。これにより、通常、編集-コンパイル-実行スペックサイクルが遅くなりますが、コンパイルされた言語に慣れている人にとっては、より親しみやすいワークフローです。また、TypeScript でスペックを記述してブラウザで実行する場合の唯一の選択肢でもあります。
Jasmine と連携するその他のソフトウェア
Jasmine 5.x を Karma で使用できますか?
おそらくそうです。karma-jasmine 5.1(執筆時点での最新バージョンであり、おそらく最終バージョン)はjasmine-core 5.xと互換性があるようです。 package.json
でNPMオーバーライドを使用して、karma-jasmineの依存関係の指定を上書きできるはずです。
{
// ...
"overrides": {
"karma-jasmine": {
"jasmine-core": "^5.0.0"
}
}
}
Karma で新しい Jasmine の機能が利用できないのはなぜですか?
思っているよりも古いjasmine-coreバージョンを使用している可能性があります。karma-jasmineはjasmine-core 4.xへの依存関係を宣言します。その結果、より新しいバージョンもインストールしていても、Karmaはjasmine-core 4.xを使用します。前の質問で説明されているように、NPMオーバーライドを追加することで修正できる場合があります。
zone.js に関する問題が発生しました。助けてもらえますか?
zone.js関連の問題は、Angularプロジェクトに報告してください。
Zone.jsはJasmineを大幅にモンキーパッチし、いくつかの主要な内部機能を独自の実装に置き換えます。ほとんどの場合、これは正常に機能します。ただし、それが原因で発生する問題は、定義上、Jasmineではなくzone.jsのバグです。
testing-library の waitFor 関数で Jasmine のマッチャーを使用するにはどうすればよいですか?
expect
の代わりにthrowUnless
を使用してください。
await waitFor(function() {
throwUnless(myDialogElement).toHaveClass('open');
});
webdriver.io で expect() が正しく機能しないのはなぜですか?
@wdio/jasmine-framework
は、Jasmineのexpect
をJasmineと互換性のない別のものに置き換えます。そのexpect
APIについては、Webdriver.IOのドキュメントを参照してください。
expect
の置き換えに加えて、Webdriver.IOはJasmineの内部構造の一部をモンキーパッチします。Webdriver.IOが存在する場合にのみ発生するバグは、JasmineではなくWebdriver.IOに報告する必要があります。
スペックの記述
describe
、it
、beforeEach
などに、通常の関数とアロー関数のどちらを渡すべきですか?
describe
の場合は問題ありません。it
、beforeEach
、およびafterEach
の場合は、通常の関数を使用することをお勧めします。 Jasmineはユーザーコンテキストを作成し、各it
、beforeEach
、およびafterEach
関数にthis
として渡します。これにより、これらの関数間で変数を簡単に渡し、各スペックの後にそれらがクリーンアップされることを確認できます。ただし、アロー関数では、this
が字句的にバインドされているため、機能しません。したがって、ユーザーコンテキストを使用する場合は、通常の関数を使用する必要があります。
包含する describe
の beforeEach
の前にコードを実行するにはどうすればよいですか?Jasmine に rspec の let
に相当するものはありますか?
簡単な答えは、できません。したがって、内部のdescribe
が、外側のdescribe
によって行われた設定を元に戻したり、上書きしたりする必要がないように、テスト設定をリファクタリングする必要があります。
この質問は通常、人々が次のようなスイートを作成しようとするときに発生します。
// DOES NOT WORK
describe('When the user is logged in', function() {
let user = MyFixtures.anyUser
beforeEach(function() {
// Do something, potentially complicated, that causes the system to run
// with `user` logged in.
});
it('does some things that apply to any user', function() {
// ...
});
describe('as an admin', function() {
beforeEach(function() {
user = MyFixtures.adminUser;
});
it('shows the admin controls', function() {
// ...
});
});
describe('as a non-admin', function() {
beforeEach(function() {
user = MyFixtures.nonAdminUser;
});
it('does not show the admin controls', function() {
// ...
});
});
});
これは、ユーザーがすでにログインした後で内部のbeforeEach
関数が実行されるため、一部が機能しません。一部のテストフレームワークは、内部のdescribe
の設定の一部が、外側のdescribe
の設定の一部よりも前に実行できるように、テスト設定を並べ替える方法を提供します。RSpecのlet
ブロックがその例です。Jasmineはそのような機能を提供していません。経験を通じて、設定フロー制御が内側と外側のdescribe
の間を行き来すると、理解しにくく、変更しにくいスイートになることがわかりました。代わりに、各部分が依存するすべての設定の後で発生するように設定コードをリファクタリングしてみてください。通常、これは外側のbeforeEach
の内容を内側のスペックまたはbeforeEach
にインライン化することを意味します。これによりコードの重複が過度になる場合は、非テストコードと同様に、通常の関数で処理できます。
describe('When the user is logged in', function() {
it('does some things that apply to any user', function() {
logIn(MyFixtures.anyUser);
// ...
});
describe('as an admin', function() {
beforeEach(function() {
logIn(MyFixtures.adminUser);
});
it('shows the admin controls', function() {
// ...
});
});
describe('as a non-admin', function() {
beforeEach(function() {
logIn(MyFixtures.nonAdminUser);
});
it('does not show the admin controls', function() {
// ...
});
});
function logIn(user) {
// Do something, potentially complicated, that causes the system to run
// with `user` logged in.
}
});
Jasmine がスタックトレースなしで例外を表示するのはなぜですか?
JavaScriptでは、任意の値がスローされたり、任意の値を伴うPromiseが拒否されたりする可能性があります。ただし、Error
オブジェクトのみにスタックトレースがあります。したがって、Error
以外のものがスローされたり、Error
以外のものが指定されたPromiseが拒否されたりすると、表示するスタックトレースがないため、Jasmineはスタックトレースを表示できません。
この動作はJavaScriptランタイムによって制御されており、Jasmineが変更できるものではありません。
// NOT RECOMMENDED
describe('Failures that will not have stack traces', function() {
it('throws a non-Error', function() {
throw 'nope';
});
it('rejects with a non-Error', function() {
return Promise.reject('nope');
});
});
// RECOMMENDED
describe('Failures that will have stack traces', function() {
it('throws an Error', function() {
throw new Error('nope');
});
it('rejects with an Error', function() {
return Promise.reject(new Error('nope'));
});
});
Jasmine はパラメーター化されたテストをサポートしていますか?
直接的にはできません。しかし、テストスイートは単なるJavaScriptであるため、とにかく実行できます。
function add(a, b) {
return a + b;
}
describe('add', function() {
const cases = [
{first: 3, second: 3, sum: 6},
{first: 10, second: 4, sum: 14},
{first: 7, second: 1, sum: 8}
];
for (const {first, second, sum} of cases) {
it(`returns ${sum} for ${first} and ${second}`, function () {
expect(add(first, second)).toEqual(sum);
});
}
});
マッチャーの失敗メッセージに詳細情報を追加するにはどうすればよいですか?
スペックに複数の類似したexpectがある場合、どの失敗がどのexpectに対応するかを判断するのが難しい場合があります。
it('has multiple expectations', function() {
expect(munge()).toEqual(1);
expect(spindle()).toEqual(2);
expect(frobnicate()).toEqual(3);
});
Failures:
1) has multiple expectations
Message:
Expected 0 to equal 1.
Stack:
Error: Expected 0 to equal 1.
at <Jasmine>
at UserContext.<anonymous> (withContextSpec.js:2:19)
at <Jasmine>
Message:
Expected 0 to equal 2.
Stack:
Error: Expected 0 to equal 2.
at <Jasmine>
at UserContext.<anonymous> (withContextSpec.js:3:21)
at <Jasmine>
このようなスペックの出力をより明確にする方法は3つあります。
- 各expectを独自のスペックに入れます。(これは時々良い考えですが、常にそうではありません。)
- カスタムマッチャーを記述します。(これは時々努力する価値がありますが、常にそうではありません。)
- withContextを使用して、マッチャーの失敗メッセージに追加のテキストを追加します。
以下は、上記と同じスペックですが、withContext
を使用するように変更されています。
it('has multiple expectations with some context', function() {
expect(munge()).withContext('munge').toEqual(1);
expect(spindle()).withContext('spindle').toEqual(2);
expect(frobnicate()).withContext('frobnicate').toEqual(3);
});
Failures:
1) has multiple expectations with some context
Message:
munge: Expected 0 to equal 1.
Stack:
Error: munge: Expected 0 to equal 1.
at <Jasmine>
at UserContext.<anonymous> (withContextSpec.js:8:40)
at <Jasmine>
Message:
spindle: Expected 0 to equal 2.
Stack:
Error: spindle: Expected 0 to equal 2.
at <Jasmine>
at UserContext.<anonymous> (withContextSpec.js:9:44)
at <Jasmine>
非同期テスト
どの非同期スタイルを使用すべきですか?また、その理由は?
async
/await
スタイルを最初に選択する必要があります。ほとんどの開発者は、そのスタイルでエラーのないスペックをはるかに簡単に記述できます。Promiseを返すスペックは少し記述が難しいですが、より複雑なシナリオで役立つ可能性があります。コールバックスタイルのスペックはエラーが発生しやすく、可能な場合は避ける必要があります。
コールバックスタイルのスペックには2つの大きな欠点があります。1つ目は、実行の流れを視覚化するのが難しいことです。これにより、実際に完了する前にdone
コールバックを呼び出すスペックを簡単に記述できます。2つ目は、エラーを正しく処理するのが難しいことです。このスペックを考えてみましょう。
it('sometimes fails to finish', function(done) {
doSomethingAsync(function(result) {
expect(result.things.length).toEqual(2);
done();
});
});
result.things
が未定義の場合、result.things.length
へのアクセスはエラーをスローし、done
が呼び出されるのを防ぎます。スペックは最終的にタイムアウトしますが、かなりの遅延の後のみです。エラーは報告されます。ただし、ブラウザとNodeが未処理の例外に関する情報を公開する方法のため、エラーの原因を示すスタックトレースやその他の情報は含まれません。
これを修正するには、各コールバックをtry-catchでラップする必要があります。
it('finishes and reports errors reliably', function(done) {
doSomethingAsync(function(result) {
try {
expect(result.things.length).toEqual(2);
} catch (err) {
done.fail(err);
return;
}
done();
});
});
これは面倒で、エラーが発生しやすく、忘れられる可能性があります。コールバックをPromiseに変換する方が良い場合がよくあります。
it('finishes and reports errors reliably', async function() {
const result = await new Promise(function(resolve, reject) {
// If an exception is thrown from here, it will be caught by the Promise
// constructor and turned into a rejection, which will fail the spec.
doSomethingAsync(resolve);
});
expect(result.things.length).toEqual(2);
});
コールバックスタイルのスペックは、いくつかの状況でまだ役立ちます。一部のコールバックベースのインターフェースは、Promise化が困難であったり、Promise化してもあまりメリットが得られなかったりします。しかし、ほとんどの場合、async
/await
、または少なくともPromiseを使用して、信頼性の高いスペックを記述する方が簡単です。
一部の非同期スペックの失敗が、スイートエラーや別のスペックの失敗として報告されるのはなぜですか?
非同期コードから例外がスローされたり、未処理のPromiseの拒否が発生したりすると、それを引き起こしたスペックはコールスタックに存在しなくなります。したがって、Jasmineには、エラーが発生した場所を特定する信頼できる方法がありません。Jasmineが最もできることは、エラーが発生したときに実行されていたスペックまたはスイートに関連付けることです。正しく記述されたスペックは、完了を通知した後にエラーをトリガーしたり(または他の何かをしたり)しないため、これは通常正しい答えです。
スペックが実際には完了する前に完了を通知する場合、問題になります。次の2つの例を考えてみましょう。どちらも、完了時にコールバックを呼び出すdoSomethingAsync
関数をテストします。
// WARNING: does not work correctly
it('tries to be both sync and async', function() {
// 1. doSomethingAsync() is called
doSomethingAsync(function() {
// 3. The callback is called
doSomethingThatMightThrow();
});
// 2. Spec returns, which tells Jasmine that it's done
});
// WARNING: does not work correctly
it('is async but signals completion too early', function(done) {
// 1. doSomethingAsync() is called
doSomethingAsync(function() {
// 3. The callback is called
doSomethingThatThrows();
});
// 2. Spec calls done(), which tells Jasmine that it's done
done();
});
どちらの場合も、スペックは完了したことを通知しますが、実行を継続し、後でエラーを引き起こします。エラーが発生するまでに、Jasmineはすでにスペックが合格したと報告し、次のスペックの実行を開始しています。エラーが発生する前に、Jasmineが終了している場合もあります。その場合、エラーはまったく報告されません。
修正は、スペックが本当に完了するまで完了を通知しないようにすることです。これは、コールバックで行うことができます。
it('signals completion at the right time', function(done) {
// 1. doSomethingAsync() is called
doSomethingAsync(function() {
// 2. The callback is called
doSomethingThatThrows();
// 3. If we get this far without an error being thrown, the spec calls
// done(), which tells Jasmine that it's done
done();
});
});
しかし、信頼性の高い非同期スペックを記述するには、async
/await
またはPromiseを使用する方が簡単なので、ほとんどの場合にお勧めします。
it('signals completion at the right time', async function() {
await doSomethingAsync();
doSomethingThatThrows();
});
Jasmine がスペックを並列で実行するのを停止するにはどうすればよいですか?
Jasmineは、jasmine
NPMパッケージの少なくともバージョン5.0を使用し、--parallel
コマンドライン引数を渡す場合にのみ、スペックを並行して実行します。他のすべての構成では、一度に1つのスペック(またはbefore/after)関数を実行します。並列構成でも、各スイート内でスペックとbefore/after関数は順番に実行されます。
ただし、Jasmineは、それらのユーザーが提供した関数が完了したときに通知することを期待しています。関数が実際に完了する前に完了を通知すると、次のスペックの実行がそれとインターリーブされます。これを修正するには、各非同期関数が、完全に完了した場合にのみ、コールバックを呼び出すか、返されたPromiseを解決または拒否することを確認してください。詳細については、非同期チュートリアルを参照してください。
コールバックを受け取り、Promise を返す(または async 関数である)スペックを記述できないのはなぜですか?代わりに何をすべきですか?
Jasmineは、各非同期スペックがいつ完了したかを認識して、適切なタイミングで次のスペックに進むことができるようにする必要があります。スペックがdone
コールバックを受け取る場合、それは「コールバックを呼び出したときに完了する」ことを意味します。スペックがPromiseを返す場合(明示的に、またはasync
キーワードを使用することにより)、それは「返されたPromiseが解決または拒否されたときに完了する」ことを意味します。これら2つが両方とも当てはまることはなく、Jasmineにはあいまいさを解決する方法がありません。将来の読者もスペックの意図を理解するのに苦労する可能性があります。
通常、この質問をする人は、次の2つの状況のいずれかに対処しています。1つは、Jasmineに完了を通知するためではなく、await
できるようにするためにasync
を使用しているだけか、もう1つは、複数の非同期スタイルを混在させるコードをテストしようとしている場合です。
最初のシナリオ:スペックがasync
なのは、await
できるようにするためだけの場合
// WARNING: does not work correctly
it('does something', async function(done) {
const something = await doSomethingAsync();
doSomethingElseAsync(something, function(result) {
expect(result).toBe(/*...*/);
done();
});
});
この場合、コールバックが呼び出されたときにスペックが完了することを意図しており、スペックから暗黙的に返されるPromiseは意味がありません。最良の解決策は、コールバックベースの関数をPromiseを返すように変更し、Promiseをawait
することです。
it('does something', async function(/* Note: no done param */) {
const something = await doSomethingAsync();
const result = await new Promise(function(resolve, reject) {
doSomethingElseAsync(something, function(r) {
resolve(r);
});
});
expect(result).toBe(/*...*/);
});
コールバックを使用する場合は、IIFEでasync
関数をラップできます。
it('does something', function(done) {
(async function () {
const something = await doSomethingAsync();
doSomethingElseAsync(something, function(result) {
expect(result).toBe(/*...*/);
done();
});
})();
});
または、await
をthen
に置き換えることもできます。
it('does something', function(done) {
doSomethingAsync().then(function(something) {
doSomethingElseAsync(something, function(result) {
expect(result).toBe(170);
done();
});
});
});
2番目のシナリオ:複数の方法で完了を通知するコード
// in DataLoader.js
class DataLoader {
constructor(fetch) {
// ...
}
subscribe(subscriber) {
// ...
}
async load() {
// ...
}
}
// in DataLoaderSpec.js
// WARNING: does not work correctly
it('provides the fetched data to observers', async function(done) {
const fetch = function() {
return Promise.resolve(/*...*/);
};
const subscriber = function(result) {
expect(result).toEqual(/*...*/);
done();
};
const subject = new DataLoader(fetch);
subject.subscribe(subscriber);
await subject.load(/*...*/);
});
最初のシナリオと同様に、このスペックの問題は、暗黙的に返されたPromiseを解決(または拒否)することと、done
コールバックを呼び出すことの2つの異なる方法で完了を通知することです。これは、DataLoader
クラスの潜在的な設計上の問題を示しています。通常、人々は、テスト対象のコードが一貫した方法で完了を通知することを信頼できないため、このようなスペックを記述します。サブスクライバーが呼び出される順序と、返されたPromiseが解決される順序は予測できない場合があります。さらに悪いことに、DataLoader
は、成功した場合に保留にして、失敗を通知するためにのみ返されたPromiseを使用する可能性があります。その問題があるコードの信頼できるスペックを記述するのは困難です。
修正は、テスト対象のコードが常に一貫した方法で完了を通知するように変更することです。この場合、これは、DataLoader
が成功と失敗の両方の場合に行う最後の処理が、返されたPromiseを解決または拒否することを確認することを意味します。これにより、次のように信頼性をもってテストできます。
it('provides the fetched data to observers', async function(/* Note: no done param */) {
const fetch = function() {
return Promise.resolve(/*...*/);
};
const subscriber = jasmine.createSpy('subscriber');
const subject = new DataLoader(fetch);
subject.subscribe(subscriber);
// Await the returned promise. This will fail the spec if the promise
// is rejected or isn't resolved before the spec timeout.
await subject.load(/*...*/);
// The subscriber should have been called by now. If not,
// that's a bug in DataLoader, and we want the following to fail.
expect(subscriber).toHaveBeenCalledWith(/*...*/);
});
ただし、成功と失敗を異なるチャネルで通知するコードをテストする必要があります。変更することはできません(またはしたくありません)。どうすればよいですか?
両側をPromiseに変換できます(まだPromiseでない場合)。次に、Promise.race
を使用して、最初に解決または拒否された方を待ちます。
// in DataLoader.js
class DataLoader {
constructor(fetch) {
// ...
}
subscribe(subscriber) {
// ...
}
onError(errorSubscriber) {
// ...
}
load() {
// ...
}
}
// in DataLoaderSpec.js
it('provides the fetched data to observers', async function() {
const fetch = function() {
return Promise.resolve(/*...*/);
};
let resolveSubscriberPromise, rejectErrorPromise;
const subscriberPromise = new Promise(function(resolve) {
resolveSubscriberPromise = resolve;
});
const errorPromise = new Promise(function(resolve, reject) {
rejectErrorPromise = reject;
});
const subject = new DataLoader(fetch);
subject.subscribe(resolveSubscriberPromise);
subject.onError(rejectErrorPromise);
const result = await Promise.race([subscriberPromise, errorPromise]);
expect(result).toEqual(/*...*/);
});
これは、テスト対象のコードが成功を通知するか、失敗を通知するかのいずれかであり、両方を実行することはないことを前提としていることに注意してください。失敗した場合に成功と失敗の両方を通知する可能性がある非同期コードの信頼できるスペックを記述することは、一般的に不可能です。
非同期関数が done
を複数回呼び出すことができないのはなぜですか?代わりに何をすべきですか?
Jasmine 2.x および 3.x では、コールバックベースの非同期関数は done
コールバックを何度でも呼び出すことができ、最初に呼び出されたものだけが処理を実行しました。これは、done
が複数回呼び出された場合に Jasmine が内部状態を破損するのを防ぐためでした。
その後、非同期関数は実際に完了したときにのみ完了を通知することが重要であることを学びました。スペックが Jasmine に完了を伝えた後も実行され続けると、他のスペックの実行とインターリーブしてしまいます。これにより、断続的なテストの失敗、失敗が報告されない、または 誤ったスペックで失敗が報告されるなどの問題が発生する可能性があります。このような問題は、長年にわたってユーザーの混乱やバグレポートの一般的な原因となってきました。Jasmine 4 では、非同期関数が done
を複数回呼び出すとエラーを報告することで、これらの問題をより簡単に診断できるようにしようとしています。
done
を複数回呼び出すスペックがある場合、最善策は、done
を 1 回だけ呼び出すように書き換えることです。複数の回完了を通知するスペックの一般的なシナリオと推奨される修正については、関連する FAQ を参照してください。
どうしても余分な done 呼び出しを排除できない場合は、次のように done
を最初の呼び出し以外を無視する関数でラップして、Jasmine 2-3 の動作を実装できます。ただし、これを行うスペックは依然としてバグがあり、上記の問題を引き起こす可能性があることに注意してください。
function allowUnsafeMultipleDone(fn) {
return function(done) {
let doneCalled = false;
fn(function(err) {
if (!doneCalled) {
done(err);
doneCalled = true;
}
});
}
}
it('calls done twice', allowUnsafeMultipleDone(function(done) {
setTimeout(done);
setTimeout(function() {
// This code may interleave with subsequent specs or even run after Jasmine
// has finished executing.
done();
}, 50);
}));
describe
に async 関数を渡すことができないのはなぜですか?非同期で読み込まれたデータからスペックを生成するにはどうすればよいですか?
同期関数は非同期関数を呼び出すことはできず、describe
はスクリプトタグで読み込まれるスクリプトのような同期コンテキストで使用されるため、同期である必要があります。非同期にすると、Jasmine を使用する既存のコードがすべて壊れ、Jasmine が最も普及している環境で Jasmine が使用できなくなります。
ただし、ES モジュールを使用する場合は、最上位の describe
を呼び出す前に非同期でデータをフェッチできます。代わりにこちらを
// WARNING: does not work
describe('Something', async function() {
const scenarios = await fetchSceanrios();
for (const scenario of scenarios) {
it(scenario.name, function() {
// ...
});
}
});
こちらを実行します
const scenarios = await fetchSceanrios();
describe('Something', function() {
for (const scenario of scenarios) {
it(scenario.name, function() {
// ...
});
}
});
トップレベルの await
を使用するには、スペックファイルが ES モジュールである必要があります。ブラウザーでスペックを実行している場合は、jasmine-browser-runner
2.0.0 以降を使用し、構成ファイルに "enableTopLevelAwait": true
を追加する必要があります。
非同期でデータをフェッチした後に何かをレンダリングする UI コンポーネントのように、Promise やコールバックがない非同期の動作をテストするにはどうすればよいですか?
これには主に 2 つのアプローチがあります。1 つ目は、非同期の動作を即座に (またはできるだけ即座に) 完了させ、スペック内で await
することです。以下は、React コンポーネントをテストするために enzyme および jasmine-enzyme ライブラリを使用したアプローチの例です。
describe('When data is fetched', () => {
it('renders the data list with the result', async () => {
const payload = [/*...*/];
const apiClient = {
getData: () => Promise.resolve(payload);
};
// Render the component under test
const subject = mount(<DataLoader apiClient={apiClient} />);
// Wait until after anything that's already queued
await Promise.resolve();
subject.update();
const dataList = subject.find(DataList);
expect(dataList).toExist();
expect(dataList).toHaveProp('data', payload);
});
});
スペックが待機するプロミスは、テスト対象のコードに渡されるプロミスとは無関係であることに注意してください。多くの場合、両方の場所で同じプロミスを使用しますが、テスト対象のコードに渡されるプロミスがすでに解決されている限り、それは問題ではありません。重要なことは、テスト対象のコードの await
呼び出しの後で、スペックの await
呼び出しが発生することです。
このアプローチはシンプルで効率的であり、問題が発生したときにすぐに失敗します。ただし、テスト対象のコードが複数の await
または .then()
を実行する場合、スケジューリングを適切に行うのが難しい場合があります。テスト対象のコードの非同期操作を変更すると、スペックが簡単に壊れる可能性があり、追加の await
を追加する必要が生じます。
もう 1 つのアプローチは、目的の動作が発生するまでポーリングすることです。
describe('When data is fetched', () => {
it('renders the data list with the result', async () => {
const payload = [/*...*/];
const apiClient = {
getData: () => Promise.resolve(payload);
};
// Render the component under test
const subject = mount(<DataLoader apiClient={apiClient} />);
// Wait until the DataList is rendered
const dataList = await new Promise(resolve => {
function poll() {
subject.update();
const target = subject.find(DataList);
if (target.exists()) {
resolve(target);
} else {
setTimeout(poll, 50);
}
}
poll();
});
expect(dataList).toHaveProp('data', payload);
});
});
これは最初は少し複雑で、効率がわずかに低い場合があります。また、期待されるコンポーネントがレンダリングされない場合、すぐに失敗するのではなく、(デフォルトでは 5 秒後に)タイムアウトします。しかし、変更に対してより耐性があります。テスト対象のコードにさらに多くの await
または .then()
呼び出しが追加された場合でも、通過します。
2 番目のスタイルのスペックを作成する際に、DOM Testing Library または React Testing Library が役立つ場合があります。これらのライブラリの findBy*
および findAllBy*
クエリは、上記に示したポーリング動作を実装しています。
テスト対象のコードが完了する前に発生する非同期コールバックに渡された引数についてアサートする必要があります。最適な方法は何ですか?
データのフェッチ、登録済みのコールバックの呼び出し、クリーンアップの実行、最後に返されたプロミスの解決を行う DataFetcher
クラスについて考えてみましょう。コールバックへの引数を検証するスペックを作成する最善の方法は、コールバック内で引数を保存し、完了を通知する直前にそれらが正しい値を持っていることをアサートすることです。
it("calls the onData callback with the expected args", async function() {
const subject = new DataFetcher();
let receivedData;
subject.onData(function(data) {
receivedData = data;
});
await subject.fetch();
expect(receivedData).toEqual(expectedData);
});
また、スパイを使用すると、より良いエラーメッセージを得ることができます。
it("calls the onData callback with the expected args", async function() {
const subject = new DataFetcher();
const callback = jasmine.createSpy('onData callback');
subject.onData(callback);
await subject.fetch();
expect(callback).toHaveBeenCalledWith(expectedData);
});
次のようなものを記述したくなるかもしれません。
// WARNING: Does not work
it("calls the onData callback with the expected args", async function() {
const subject = new DataFetcher();
subject.onData(function(data) {
expect(data).toEqual(expectedData);
});
await subject.fetch();
});
しかし、onData
コールバックが呼び出されない場合、期待値が実行されないため、誤って通過してしまいます。もう 1 つの一般的だが誤ったアプローチを以下に示します。
// WARNING: Does not work
it("calls the onData callback with the expected args", function(done) {
const subject = new DataFetcher();
subject.onData(function(data) {
expect(data).toEqual(expectedData);
done();
});
subject.fetch();
});
このバージョンでは、テスト対象のコードが実際に実行を完了する前にスペックが完了を通知します。これにより、スペックの実行が他のスペックとインターリーブし、誤ったエラーやその他の問題が発生する可能性があります。
拒否された Promise が原因でスペックが失敗した場合、Jasmine が常にスタックトレースを表示するとは限らないのはなぜですか?
これは、Jasmine がスタックトレースなしで例外を表示するのはなぜですか?に似ています。プロミスが理由として Error
オブジェクトで拒否された場合(例:Promise.reject(new Error("チーズ切れ"))
)、Jasmine はエラーに関連付けられたスタックトレースを表示します。プロミスが理由なしまたは Error
以外の理由で拒否された場合、Jasmine が表示するスタックトレースはありません。
未処理の Promise の拒否エラーが発生していますが、誤検知だと思います。
JavaScript ランタイムが、どのプロミス拒否が未処理と見なされるかを決定することを理解することが重要です。Jasmine は JavaScript ランタイムによって発行された未処理の拒否イベントに応答するだけです。
拒否されたプロミスを作成するだけで、最初に拒否ハンドラーをアタッチせずに JavaScript ランタイムに制御を戻すことを許可すると、未処理のプロミス拒否イベントをトリガーするのに十分な場合があります。たとえプロミスで何もしていなくても、それは当てはまります。Jasmine は、未処理の拒否はほとんどの場合、何か予期しないことが発生したことを意味し、「実際の」未処理の拒否と将来的に処理される拒否を区別する方法がないため、未処理の拒否を失敗として扱います。
このスペックを検討してください。
it('causes an unhandled rejection', async function() {
const rejected = Promise.reject(new Error('nope'));
await somethingAsync();
try {
await rejected;
} catch (e) {
// Do something with the error
}
});
拒否は最終的に try
/ catch
を介して処理されます。ただし、JS ランタイムは、スペックのその部分が実行される前に、未処理の拒否を検出します。これは、await somethingAsync()
呼び出しが制御を JS ランタイムに戻すために発生します。異なる JS ランタイムは未処理の拒否を異なる方法で検出しますが、一般的な動作は、制御がランタイムに戻る前に catch ハンドラーがアタッチされている場合、拒否は未処理と見なされないということです。ほとんどの場合、これはコードを少し並べ替えることで実現できます。
it('causes an unhandled rejection', async function() {
const rejected = Promise.reject(new Error('nope'));
let rejection;
try {
await rejected;
} catch (e) {
rejection = e;
}
await somethingAsync();
// Do something with `rejection`
});
最後の手段として、no-op キャッチハンドラーをアタッチして、未処理の拒否を抑制できます。
it('causes an unhandled rejection', async function() {
const rejected = Promise.reject(new Error('nope'));
rejected.catch(function() { /* do nothing */ });
await somethingAsync();
let rejection;
try {
await rejected;
} catch (e) {
rejection = e;
}
// Do something with `rejection`
});
スパイを構成するときに未処理の拒否を回避する方法については、どのようにして、未処理のプロミス拒否エラーをトリガーせずに拒否されたプロミスを返すようにスパイを構成できますか?も参照してください。
上記で述べたように、Jasmine はどの拒否が未処理と見なされるかを決定しません。それを変更するように求める問題をオープンしないでください。
スパイ
AJAX 呼び出しをモックするにはどうすればよいですか?
XMLHttpRequest
またはその下でそれを使用するライブラリを使用している場合は、jasmine-ajax を選択するのが適切です。これは、XMLHttpRequest
をモックするという時に複雑になる詳細を処理し、リクエストを検証してレスポンスをスタブするための優れた API を提供します。
XMLHttpRequest
とは異なり、axios や fetch などの新しい HTTP クライアント API は、Jasmine スパイを使用して手動でモックするのが簡単です。単にテスト対象のコードに HTTP クライアントを注入してください。
async function loadThing(thingId, thingStore, fetch) {
const url = `http://example.com/api/things/{id}`;
const response = await fetch(url);
thingStore[thingId] = response.json();
}
// somewhere else
await loadThing(thingId, thingStore, fetch);
次に、スペックで、スパイを注入します。
describe('loadThing', function() {
it('fetches the correct URL', function() {
const fetch = jasmine.createSpy('fetch')
.and.returnValue(new Promise(function() {}));
loadThing(17, {}, fetch);
expect(fetch).toHaveBeenCalledWith('http://example.com/api/things/17');
});
it('stores the thing', function() {
const payload = return {
id: 17,
name: 'the thing you requested'
};
const response = {
json: function() {
return payload;
}
};
const thingStore = {};
const fetch = jasmine.createSpy('fetch')
.and.returnValue(Promise.resolve(response));
loadThing(17, thingStore, fetch);
expect(thingStore[17]).toEqual(payload);
});
});
一部のブラウザで localStorage メソッドをスパイできないのはなぜですか?代わりに何をすればよいですか?
これは一部のブラウザではパスしますが、Firefox と Safari 17 では失敗します。
it('sets foo to bar on localStorage', function() {
spyOn(localStorage, 'setItem');
localStorage.setItem('foo', 'bar');
expect(localStorage.setItem).toHaveBeenCalledWith('foo', 'bar');
});
セキュリティ対策として、Firefox と Safari 17 では、localStorage
のプロパティを上書きすることが許可されていません。それらに代入することは、spyOn
が内部で行うことですが、何もしません。これはブラウザによって課せられた制限であり、Jasmine が回避する方法はありません。
1 つの代替方法は、呼び出しがどこで行われたかを検証するのではなく、localStorage
の状態を確認することです。
it('sets foo to bar on localStorage', function() {
localStorage.setItem('foo', 'bar');
expect(localStorage.getItem('foo')).toEqual('bar');
});
もう 1 つのオプションは、localStorage
の周りにラッパーを作成し、ラッパーをモックすることです。
モジュールのプロパティをスパイするにはどうすればよいですか?「aProperty はアクセスタイプ get を持っていません」、「書き込み可能として宣言されていないか、setter がありません」、「構成可能として宣言されていません」のようなエラーが発生します。
このエラーは、何か (おそらくトランスパイラー、場合によっては JavaScript ランタイム) が、モジュールのエクスポートされたプロパティを読み取り専用としてマークしたことを意味します。ES モジュール仕様では、エクスポートされたモジュールのプロパティは読み取り専用である必要があり、一部のトランスパイラーは CommonJS モジュールを出力する場合でも、その要件に準拠します。プロパティが読み取り専用としてマークされている場合、Jasmine はそれをスパイに置き換えることができません。
どの環境にいるかに関係なく、モックしたいものの依存性注入を使用し、スペックからスパイまたはモックオブジェクトを注入することで、問題を回避できます。このアプローチは通常、スペックとテスト対象のコードの保守性の向上につながります。モジュールをモックする必要があることは、コードが密結合になっている兆候であることが多く、テストツールで回避するよりも、結合を修正する方が賢明な場合があります。
環境によっては、モジュールモックを有効にできる場合があります。詳細については、モジュールモックガイドを参照してください。
未処理の Promise の拒否エラーをトリガーせずに、拒否された Promise を返すようにスパイを構成するにはどうすればよいですか?
JavaScript ランタイムが、どのプロミス拒否が未処理と見なされるかを決定することを理解することが重要です。Jasmine は JavaScript ランタイムによって発行された未処理の拒否イベントに応答するだけです。
拒否されたプロミスを作成するだけで、拒否ハンドラーをアタッチせずに JavaScript ランタイムに制御を戻すことを許可すると、Node およびほとんどのブラウザで未処理の拒否イベントをトリガーするのに十分です。たとえプロミスで何もしていなくても、それは当てはまります。Jasmine は、未処理の拒否はほとんどの場合、何か予期しないことが発生したことを意味するため、未処理の拒否を失敗として扱います。(参考: 未処理のプロミス拒否エラーが発生していますが、誤検出だと思います。)
このスペックを検討してください。
it('might cause an unhandled promise rejection', async function() {
const foo = jasmine.createSpy('foo')
.and.returnValue(Promise.reject(new Error('nope')));
await expectAsync(doSomething(foo)).toBeRejected();
});
スペックは拒否されたプロミスを作成します。すべてが正しく動作する場合、それは最終的に非同期マッチャーによって処理されます。しかし、doSomething
が foo
の呼び出しに失敗した場合、または拒否を渡すことに失敗した場合、ブラウザまたは Node は未処理のプロミス拒否イベントをトリガーします。Jasmine は、そのイベントの実行時に実行中のスイートまたはスペックの失敗としてそれを扱います。
1 つの修正方法は、スパイが実際に呼び出された場合にのみ、拒否されたプロミスを作成することです。
it('does not cause an unhandled promise rejection', async function() {
const foo = jasmine.createSpy('foo')
.and.callFake(() => Promise.reject(new Error('nope')));
await expectAsync(doSomething(foo)).toBeRejected();
});
rejectWith スパイ戦略を使用すると、これを少し明確にすることができます。
it('does not cause an unhandled promise rejection', async function() {
const foo = jasmine.createSpy('foo')
.and.rejectWith(new Error('nope'));
await expectAsync(doSomething(foo)).toBeRejected();
});
上記で述べたように、Jasmine はどの拒否が未処理と見なされるかを決定しません。それを変更するように求める問題をオープンしないでください。
貢献
Jasmine の開発を手伝いたいのですが、どこから始めればよいですか?
ご協力ありがとうございます。Jasmine チームは Jasmine で作業する時間が限られているため、コミュニティからのすべての支援に感謝しています。
Github Issue
Jasmine がサポートできると思われる Github issue が報告された場合、issue に「help needed」のラベルを付けます。このラベルは、誰かが自分で実装するのに十分な情報が会話に含まれていると私たちが信じていることを意味します。(いつも正しいとは限りません。さらに質問がある場合は、質問してください)。
新しいアイデア
もしGitHubのissueにまだ登録されていないアイデアをお持ちでしたら、ぜひ提案してください。プルリクエストを提出する前に、issueを開いてアイデアについて議論することを推奨します(必須ではありません)。すべての提案が受け入れられるわけではないため、多くの作業をする前に確認することをお勧めします。
Jasmine は、それ自体をテストするために何を使用していますか?
JasmineはJasmineをテストするためにJasmineを使用しています。
Jasmineのテストスイートは、Jasmineのコピーを2つロードします。1つ目はlib/
にあるビルド済みのファイルからロードされます。2つ目はjasmineUnderTest
と呼ばれ、src/
にあるソースファイルから直接ロードされます。最初のJasmineはスペックを実行するために使用され、スペックはjasmineUnderTest
の関数を呼び出します。
これにはいくつかの利点があります。
- 開発者はJasmineを開発するためにJasmineを使用することで、Jasmineの設計に関するフィードバックを得ることができます。
- 開発者は、(何もしないことで) Jasmineの最後にコミットされたバージョンに対してテストするか、(最初にビルドすることで) 現在のコードに対してテストするかを選択できます。
- Jasmineに新しく導入されたバグのためにJasmineのテストが実行できない状態になることはありません。開発者は、スペックがグリーンになるまでビルドしないことでその状況を回避し、
git checkout lib
を実行するだけでそこから抜け出すことができます。 - ビルドステップが必要ないため、ファイルを保存してからテスト実行の結果を確認するまでにかかる時間が2秒未満になる場合があります。
この設定方法について興味がある場合は、requireCore.jsとdefineJasmineUnderTest.jsを参照してください。
Jasmine が独自のモジュールシステムを持っているのはなぜですか? Babel と Webpack を使用しないのはなぜですか?
簡単に言うと、JasmineはBabelとWebpackの両方よりも前に存在しており、これらのツールに移行することは、JasmineがInternet Explorerのような非ES2017環境のサポートを終了した際にほとんど解消された、比較的小さなメリットのために多くの作業を必要とするでしょう。Jasmineの多くはまだES5で記述されていますが、新しい言語機能も使用できるようになりました。
Jasmineは、そのほとんどの期間において、新しいJavaScript機能をサポートしていないブラウザで実行する必要がありました。つまり、コンパイルされたコードでは、アロー関数、async
/await
、Promise
、Symbol
、Map
、Set
などの新しい構文やライブラリ機能を使用できませんでした。その結果、特定の狭いコンテキスト(asyncマッチャーなど)を除いて、移植性のないライブラリ機能は一切使用せずにES5構文で記述されました。
では、なぜBabelとWebpackを採用しないのでしょうか?一つには、Jasmineがこれらのツールによって想定されたいくつかの仮定を破る、奇妙なスペースに適合しているからです。Jasmineはアプリケーションとライブラリの両方であり、アプリケーションとして機能しているときでさえ、JavaScriptのランタイム環境を安全に変更することはできません。もしJasmineが欠落しているライブラリ機能のポリフィルを追加した場合、これらの機能に依存するコードのスペックは、それらの機能を持たないブラウザで誤って合格する可能性があります。ポリフィルが導入されないことを保証する方法でBabelとWebpack(または他のバンドラー)を設定する方法はまだ見つけていません。また、仮に見つけたとしても、メリットは比較的小さいでしょう。ES6の代わりにES5構文を記述することは、幅広いブラウザをサポートする上で簡単な部分でした。難しい部分は、主に欠落しているライブラリ機能やその他の非互換性に対処することであり、これらは手動で解決する必要がありました。
Jasmineの既存のビルドツールは、シンプルさ、スピード、および非常に低いメンテナンスコストという利点を持っています。もし変更が大幅な改善であるならば、より新しいものに切り替えることに反対はしません。しかし、これまでのところ、この分野で保守的であることが、フロントエンドのビルドツールの混乱をかなり回避し、ユーザーに利益をもたらす作業に時間を使うことを可能にしました。
サポートされている一部の環境にないものに依存する機能を開発するにはどうすればよいですか?
Jasmineのすべての機能を、サポートされているすべてのブラウザとNodeバージョンで利用できるように努めていますが、そうすることが理にかなわない場合もあります。たとえば、promiseを返すスペックのサポートは2.7.0で追加されましたが、Jasmineは4.0.0までpromiseがない環境でも実行し続けました。すべての環境で動作しないもののスペックを記述するには、必要な言語/ランタイム機能が存在するかどうかを確認し、存在しない場合はスペックをペンディングとしてマークします。この方法の例については、spec/helpers/checkForUrl.jsと、それが定義するrequireUrls
関数の使用法を参照してください。
オブジェクトが存在しない可能性のある型のインスタンスであるかどうかを安全に確認する方法の例については、src/core/base.jsのis*メソッドを参照してください。