JavaScript相互運用オブジェクトのモック作成方法
このチュートリアルでは、実際のインプリメンテーションを使用せずに、相互運用インスタンスメンバーをテストできるように、JSオブジェクトのモック作成方法を学習します。
背景と動機
#Dartでのクラスのモック作成は、通常、インスタンスメンバーの上書きによって行われます。しかし、拡張型を使用して相互運用型を宣言するため、すべての拡張型メンバーは静的にディスパッチされ、そのため上書きは使用できません。この制限は拡張メンバーにも当てはまります。そのため、インスタンス拡張型または拡張メンバーはモック作成できません。
これは、`external`以外の拡張型メンバーすべてに適用されますが、`external`相互運用メンバーは、JS値のメンバーを呼び出すため特殊です。
extension type Date(JSObject _) implements JSObject {
external int getDay();
}
使用方法セクションで説明されているように、`getDay()`を呼び出すと、JSオブジェクトの`getDay()`が呼び出されます。そのため、別の`JSObject`を使用することで、`getDay`の異なる実装を呼び出すことができます。
これを行うには、呼び出されるとDart関数を呼び出す`getDay`プロパティを持つJSオブジェクトを作成する何らかのメカニズムが必要です。簡単な方法は、JSオブジェクトを作成し、プロパティ`getDay`を変換されたコールバック(例:)に設定することです。
final date = Date(JSObject());
date['getDay'] = (() => 0).toJS;
これは機能しますが、エラーが発生しやすく、多くの相互運用メンバーを使用する場合はスケールしません。また、ゲッターやセッターも適切に処理されません。代わりに、`createJSInteropWrapper`と`@JSExport`を組み合わせて、すべての`external`インスタンスメンバーの実装を提供する型を宣言する必要があります。
モック作成の例
#import 'dart:js_interop';
import 'package:expect/minitest.dart';
// The Dart class must have `@JSExport` on it or at least one of its instance
// members.
@JSExport()
class FakeCounter {
int value = 0;
@JSExport('increment')
void renamedIncrement() {
value++;
}
void decrement() {
value--;
}
}
extension type Counter(JSObject _) implements JSObject {
external int value;
external void increment();
void decrement() {
value -= 2;
}
}
void main() {
var fakeCounter = FakeCounter();
// Returns a JS object whose properties call the relevant instance members in
// `fakeCounter`.
var counter = createJSInteropWrapper<FakeCounter>(fakeCounter) as Counter;
// Calls `FakeCounter.value`.
expect(counter.value, 0);
// `FakeCounter.renamedIncrement` is renamed to `increment`, so it gets
// called.
counter.increment();
expect(counter.value, 1);
expect(fakeCounter.value, 1);
// Changes in the fake affect the wrapper and vice-versa.
fakeCounter.value = 0;
expect(counter.value, 0);
counter.decrement();
// Because `Counter.decrement` is non-`external`, we never called
// `FakeCounter.decrement`.
expect(counter.value, -2);
}
`@JSExport`を使用すると、`createJSInteropWrapper`で使用できるクラスを宣言できます。`createJSInteropWrapper`は、クラスの各インスタンスメンバー名(または名前変更)をJSコールバックにマッピングするオブジェクトリテラルを作成します。これは`Function.toJS`を使用して作成されます。呼び出されると、JSコールバックは順番にインスタンスメンバーを呼び出します。上記の例では、`counter.value`の取得と設定は`fakeCounter.value`の取得と設定を行います。
クラスのアノテーションを省略し、特定のメンバーのみにアノテーションを付けることで、クラスの一部のメンバーのみをエクスポートするように指定できます。(継承を含む)より専門的なエクスポートの詳細については、`@JSExport`のドキュメントを参照してください。
このメカニズムはテストのみに限定されません。これを使用して、任意のDartオブジェクトのJSインターフェースを提供し、事前に定義されたインターフェースを使用してDartオブジェクトをJSにエクスポートすることができます。
特に明記されていない限り、このサイトのドキュメントはDart 3.5.3を反映しています。ページの最終更新日は2024年8月1日です。 ソースコードを見る または 問題を報告する。