简体   繁体   中英

How to test specific hardware features with Robolectric on android?

I started writing a robolectric test around hardware specific features, like sensors and camera (front and back).

Imagine this class:

class CheckHardware {

    private bolean hasCamera(Context context) {
        PackageManager pm = context.callingActivityContext
            .getPackageManager();
        // camera support
        Boolean frontCam = pm.hasSystemFeature("android.hardware.camera.front");
        Boolean rearCam = pm.hasSystemFeature(PackageManager.FEATURE_CAMERA);
        if (frontCam || rearCam) {
            return true;
        }
        return false;
    }
}

So i want to test different scenarios where there is a front camera and a rear camera, only a front camera or no camera at all. In my app its a bit more complicated but hope this makes it easier to understand what i mean.

For now i did it like this, which feels a bit odd.

RobolectricPackageManager pm = (RobolectricPackageManager) Robolectric.application.getPackageManager();
pm.setSystemFeature(PackageManager.FEATURE_CAMERA, true);

I thought about writing my own test runner, so for all expected hardware settings a specific runner like this

public class WithCameraTestRunner extends RobolectricTestRunner {   
  @Override
  public void setupApplicationstate(RobolectricConfig robolectricConfig) {
    super.setupApplicationState(robolectricConfig);
    ShadowApplication shadowApplication = shadowOf(Robolectric.application);
    shadowApplication.setPackageName(robolectricConfig.getPackageName());
    RobolectricPackageManager pm = new RobolectricPackageManager(Robolectric.application, robolectricConfig)
    pm.setSystemFeature(PackageManager.FEATURE_CAMERA, true);
    shadowApplication.setPackageManager(pm);
  }
}

Not quite happy with that either as i want to test different scenarios in the same test.

Any ideas? Whats the best approach for this?

This can be done with JUnit rules. I wrote this example while using JUnit 4.8.2.

Here is the test class:

public class CameraTest {

    @Rule
    public CameraRule rule = new CameraRule();

    @Test
    @EnableCamera
    public void testWithCamera() {
        Assert.assertTrue(CameraHolder.CAMERA);
    }

    @Test
    public void testWithoutCamera() {
        Assert.assertFalse(CameraHolder.CAMERA);
    }

}

Here is the rule:

import org.junit.rules.MethodRule;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.Statement;

public class CameraRule implements MethodRule {

    @Override
    public Statement apply(Statement base, FrameworkMethod method, Object target) {
        boolean camera = false;
        if (method.getAnnotation(EnableCamera.class) != null) {
            camera = true;
        }

        return new CameraStatement(camera, base);
    }

}

I included the imports so you can see from where they originated. Statement is an interface with only a function evaluate() .

Here is the statement class:

import org.junit.runners.model.Statement;

public class CameraStatement extends Statement {

    private boolean mEnabled;
    private Statement mStatement;

    public CameraStatement(boolean enabled, Statement statement) {
        mEnabled = enabled;
        mStatement = statement;
    }

    @Override
    public void evaluate() throws Throwable {
        CameraHolder.CAMERA = mEnabled;
        mStatement.evaluate();
    }

}

Instead of an enabled boolean you can easily pass in an enumeration or a Set of enumerations for which features you want enabled. Make sure to disable all features that are not explicitly enabled. In this example, if you don't explicitly set enabled to true it will be false .

Here is the code for the annotation itself:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EnableCamera {
}

This would allow you to use this to annotate the function with @EnableCamera in addition to @Test . If you want to have multiple features on for the same test you can put multiple annotations on the function.

For the sake of completeness here is the implementation of CameraHolder (but you won't need this):

public class CameraHolder {
    public static boolean CAMERA = false;
}

I tried with robolectric, but my version of robolectric didn't have the function you were using:

RobolectricPackageManager pm = (RobolectricPackageManager) Robolectric.application.getPackageManager();
// My version of robolectric didn't have this function.
pm.setSystemFeature(PackageManager.FEATURE_CAMERA, true);

I would probably have HardwareInfo class with methods hasBackCamera() and hasFrontCamera() . This class will require four tests - for each case of camera presence. And all of them require you manipulation with Robolectric .

For class that is doing real job I will mock HardwareInfo and have kind of setup methods:

HardwareInfo infoWithFromCameraAndBackCamera() {
    HardwareInfo info = mock( HardwareInfo.class );

    when( info.hasBackCamera() ).thenReturn( true );
    when( info.hasFrontCamera() ).thenReturn( true );

    return info;
}

You could automate it/write less code as answer above/below with JUnit rules.

Maybe I will go further I'm not sure if it's suitable for your case. I would get rid of HardwareInfo usage directly in classes. Basically I would have strategies classes which will behave correctly in specific hardware cases (similar to How to Replace (and not just move) Conditional Logic with Strategy? ). But again it depends on your situation. I wouldn't go with strategies if it's only one/two places where these conditions are used.

@Test
public void hasCamera(){
    MainActivity activity = Robolectric.buildActivity( MainActivity.class )
            .create()
            .resume()
            .get();

    PackageManager packageManager = activity.getApplication().getPackageManager();
    ShadowPackageManager shadowPackageManager = shadowOf(packageManager);

    shadowPackageManager.setSystemFeature(PackageManager.FEATURE_CAMERA, true);
    assert(CameraUtils.checkCameraHardware(activity));

    shadowPackageManager.setSystemFeature(PackageManager.FEATURE_CAMERA, false);
    assert(!CameraUtils.checkCameraHardware(activity));

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM