SyntaxHighlighter

2013年1月27日日曜日

RobolectricでBluetoothAdapterをMockする

RobolectricでAndroidのクラスをMockする時はShadowを使うのが普通で、独自拡張が必要な場合は拡張(あるいは新規作成)したShadowクラスをRobolectric.bindShadowClassすれば良い。

つまり、BluetoothAdapterのShadowクラスを作る時は、ShadowBluetoothAdapterの拡張Shadowクラスを作ると思う。ところが、これを@Beforeや@TestでbindShadowClassしてもうまくいかない場合がある。

結論から書くと、ShadowBluetoothAdapterのgetDefaultAdapterの返り値を上書くのがポイント。
ShadowBluetoothAdapterのgetDefaultAdapterは、Robolectric.applicationで保持しているBluetoothAdapterを返す仕組みになっていて、これが生成されるのはRobolectric.applicationが生成されたときである。
つまり、@Beforeや@TestでbindShadowClassしてもgetDefaultAdapterの戻り値が拡張前のShadowBluetoothAdapterがbindされたBluetoothAdapterになってしまうのである。

回避するには、次の2つの方法がある。

1. getDefaultAdapterをOverrideする。
getDefaultAdapterをOverrideして、Robolectric.newInstanceOf(BluetoothAdapter.class.getName())を返すようにしておく。こうすることで、getDefaultAdapterをする時点で登録されていたShadowBluetoothAdapterがbindされたBluetoothAdapterが返されるようになる。
シングルトンにするかどうかはテストの性質などを考えて適宜調整すればOK。

2. Robolectric.applicationの生成前にbindShadowClassする。
これをするには、RobolectricRunnerを継承した独自Runnerを作って、bindShadowClassesをOverrideして、その中でbindShadowClassすれば良い。こうすれば、Robolectric.applicationの生成前に、BluetoothAdapterクラスに拡張したShadowBluetoothAdapterを紐付けられる。

サンプルコードは以下(1の方法で実装している)