Search code examples
javaandroidbluetoothrobolectricnosuchmethoderror

How to override Robolectric standard shadow class with custom class?


I am writing a simple application that connects to a Bluetooth low energy device. The code that connects to the device is on a HandlerThread so, to make the handler thread work in a unit test I used robolectric.

After using robolectric, the handler thread work perfectly, but there was another problem.

Robolectric does not allow me to mock the BluetoothDevice class as the robolectric ShadowBluetoothDevice class does not contain the method connectGatt(...). So, at runtime I get the error:

java.lang.NoSuchMethodError: android.bluetooth.BluetoothDevice.connectGatt(Landroid/content/Context;ZLandroid/bluetooth/BluetoothGattCallback;)Landroid/bluetooth/BluetoothGatt;

at com.example.BLEGattTest.setup(BLEGattTest.java:45)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
at org.robolectric.RobolectricTestRunner$2.evaluate(RobolectricTestRunner.java:250)
at org.robolectric.RobolectricTestRunner.runChild(RobolectricTestRunner.java:176)
at org.robolectric.RobolectricTestRunner.runChild(RobolectricTestRunner.java:49)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.robolectric.RobolectricTestRunner$1.evaluate(RobolectricTestRunner.java:142)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:234)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:74)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

So, I was thinking, if I could just override the ShadowBluetoothDevice, I can actually make this work?

My Code:

@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
    public class BLEGattTest {

        private BLEGattScanner scanner;

        private Customer customer;

        private BluetoothDevice device;

        @Before
        public void setup(){

            customer = new Customer();
            customer.setDevice(device);

            device = Mockito.mock(BluetoothDevice.class);
            when(device.connectGatt(any(Context.class), anyBoolean(), any(BluetoothGattCallback.class))).thenReturn(null);

            scanner = new BLEGattScanner(RuntimeEnvironment.application);
            scanner.start();
        }

        @After
        public void tearDown() throws InterruptedException {
            if(scanner != null){
                scanner.stopThread();
                scanner.join(1000);
            }
        }

        @Test
        public void testMocks(){
            Assert.assertEquals("asdf", device.getName());
        }
    }

Solution

  • You add the device and after that, you mock it. That cannot work - you need to mock it and then set the device.

    At customer.setDevice(device) the device is undefined.