I am starting with an Activity based off of this ShakeActivity and I want to write some unit tests for it. I have written some small unit tests for Android activities befor
How can I send fake data to the accelerometer from a unit test?
AFAIK, you can't.
Have your shaker logic accept a pluggable data source. In the unit test, supply a mock. In production, supply a wrapper around the accelerometer.
Or, don't worry about unit testing the shaker itself, but rather worry about unit testing things that use the shaker, and create a mock shaker.
Well, you can write an interface.
interface IAccelerometerReader {
public float[] readAccelerometer();
}
The write an AndroidAccelerometerReader
and FakeAccelerometerReader
. Your code would use IAccelerometerReader
but you can swap in the Android or Fake readers.
public class SensorService implements SensorEventListener {
/**
* Accelerometer values
*/
private float accValues[] = new float[3];
@Override
public void onSensorChanged(SensorEvent event) {
if (sensorEvent.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
accValues[0] = sensorEvent.values[0];
accValues[1] = sensorEvent.values[1];
accValues[2] = sensorEvent.values[2];
}
}
}
you can test above piece of code by following way
@Test
public void testOnSensorChangedForAcceleratorMeter() throws Exception {
Intent intent=new Intent();
sensorService.onStartCommand(intent,-1,-1);
SensorEvent sensorEvent=getEvent();
Sensor sensor=getSensor(Sensor.TYPE_ACCELEROMETER);
sensorEvent.sensor=sensor;
sensorEvent.values[0]=1.2345f;
sensorEvent.values[1]=2.45f;
sensorEvent.values[2]=1.6998f;
sensorService.onSensorChanged(sensorEvent);
Field field=sensorService.getClass().getDeclaredField("accValues");
field.setAccessible(true);
float[] result= (float[]) field.get(sensorService);
Assert.assertEquals(sensorEvent.values.length,result.length);
Assert.assertEquals(sensorEvent.values[0],result[0],0.0f);
Assert.assertEquals(sensorEvent.values[1],result[1],0.0f);
Assert.assertEquals(sensorEvent.values[2],result[2],0.0f);
}
private Sensor getSensor(int type) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
Constructor<Sensor> constructor = Sensor.class.getDeclaredConstructor(new Class[0]);
constructor.setAccessible(true);
Sensor sensor= constructor.newInstance(new Object[0]);
Field field=sensor.getClass().getDeclaredField("mType");
field.setAccessible(true);
field.set(sensor,type);
return sensor;
}
private SensorEvent getEvent() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<SensorEvent> constructor = SensorEvent.class.getDeclaredConstructor(int.class);
constructor.setAccessible(true);
return constructor.newInstance(new Object[]{3});
}
No need to test the OS's accelerometer, just test your own logic that responds to the OS - in other words your SensorListener
. Unfortunately SensorEvent
is private and I could not call SensorListener.onSensorChanged(SensorEvent event)
directly, so had to first subclass SensorListener with my own class, and call my own method directly from the tests:
public class ShakeDetector implements SensorEventListener {
@Override
public void onSensorChanged(SensorEvent event) {
float x = event.values[0];
float y = event.values[1];
float z = event.values[2];
onSensorUpdate(x, y, z);
}
public void onSensorUpdate(float x, float y, float z) {
// do my (testable) logic here
}
}
Then I can call onSensorUpdated
directly from my test code, which simulates the accelerometer firing.
private void simulateShake(final float amplitude, int interval, int duration) throws InterruptedException {
final SignInFragment.ShakeDetector shaker = getFragment().getShakeSensorForTesting();
long start = System.currentTimeMillis();
do {
getInstrumentation().runOnMainSync(new Runnable() {
@Override
public void run() {
shaker.onSensorUpdate(amplitude, amplitude, amplitude);
}
});
Thread.sleep(interval);
} while (System.currentTimeMillis() - start < duration);
}
My solution to this ended up way simpler then I expected. I'm not really testing the accelerometer so much as I am testing the application's response to an event raised by the accelerometer, and I just needed to test accordingly. My class implements SensorListener and I wanted to test what happens onSensorChanged. The key then was to feed in some values and check my Activity's state. Example:
public void testShake() throws InterruptedException {
mShaker.onSensorChanged(SensorManager.SENSOR_ACCELEROMETER, new float[] {0, 0, 0} );
//Required because method only allows one shake per 100ms
Thread.sleep(500);
mShaker.onSensorChanged(SensorManager.SENSOR_ACCELEROMETER, new float[] {300, 300, 300});
Assert.assertTrue("Counter: " + mShaker.shakeCounter, mShaker.shakeCounter > 0);
}