1

I would like to check that my app shows an error message when the device it is running on has no camera. I have tried passing in a mock context but mockito gives an error when I try to mock the CameraManager class as it is declared final. Surely android has a simple solution for this? Here's my attempt:

public class CreateNewIdentityActivityUnitTest extends ActivityUnitTestCase<CreateNewIdentityActivity> {
    public CreateNewIdentityActivityUnitTest() {
        super(CreateNewIdentityActivity.class);
    }

    public void testErrorMessageDisplayedWhenNoCamerasExist() throws Exception {
        // Create the mock cameramanager
        // THIS LINE FAILS BECAUSE CAMERAMANAGER IS FINAL
        CameraManager mockCameraManager = mock(CameraManager.class);
        String[] cameraIdList = {};
        when(mockCameraManager.getCameraIdList()).thenReturn(cameraIdList);

        // Create the mock context
        Context mockContext = mock(Context.class);
        when(mockContext.getSystemService(Context.CAMERA_SERVICE)).thenReturn(mockCameraManager);

        // Start the activity
        Intent intent = new Intent(mockContext, CreateNewIdentityActivity.class);
        Activity activity = startActivity(intent, null, null);

        // Verify that the error message was made visible
        TextView errorTextView = (TextView)activity.findViewById(R.id.ErrorTextView);
        assertNotNull(errorTextView);
        assertEquals(View.VISIBLE, errorTextView.getVisibility());
    }
}
brnby
  • 1,433
  • 1
  • 19
  • 35

2 Answers2

1

Unfortunately, you can't mock final class.

There're few options/hacks:

  • Try to add Robolectric library and write test with it's ShadowCamera
  • Move logic related to CameraManager into a separate class and inject it in Activity. Then in the Test project, you can override this injection.
  • Pretty similar idea - create an interface OnCameraManagerInterface

    public interface OnCameraManagerInterface {
         String[] getListOfCameras() throws CameraAccessException;
    }
    

    Then implement it in the Activity:

    public class CreateNewIdentityActivity extends AppCompatActivity
            implements OnCameraManagerInterface {
        .......
        @TargetApi(Build.VERSION_CODES.LOLLIPOP)
        @Override
        public String[] getListOfCameras() throws CameraAccessException {
            return ((CameraManager)getSystemService(Context.CAMERA_SERVICE)).
                      getCameraIdList();
        }
    }
    

    And in the code, where you check camera existence - call: if (getListOfCameras().length == 0) {}

    Now, add new TestCreateNewIdentityActivity to override your CreateNewIdentityActivity:

     public class TestCreateNewIdentityActivity extends CreateNewIdentityActivity {
        @Override
        public String[] getListOfCameras() throws CameraAccessException {
            return new String[0];
        }
    }
    

    In Manifest:

    <activity android:name=".TestCreateNewIdentityActivity"
            android:theme="@style/AppTheme.NoActionBar"/>
    

    And test will look like:

    public class CreateNewIdentityActivityUnitTest extends ActivityUnitTestCase<TestCreateNewIdentityActivity> {
        public CreateNewIdentityActivityUnitTest() {
            super(TestCreateNewIdentityActivity.class);
        }
    
        public void testErrorMessageDisplayedWhenNoCamerasExist() throws Exception {
            // Verify that the error message was made visible
            TextView errorTextView = (TextView)getActivity().findViewById(R.id.ErrorTextView);
            assertNotNull(errorTextView);
            assertEquals(View.VISIBLE, errorTextView.getVisibility());
        }
    }   
    

    I'm pretty sure, it doable even without adding the TestActivity into the main source code and to manifest(to keep it in androidTest, though I didn't look)

  • Hybrid variant without creation of new activity:

        public class ActivityCameraManager {
    
        private boolean isTest = false;
        private CameraManager cameraManager;
    
        public ActivityCameraManager(CameraManager cameraManager) {
            this.cameraManager = cameraManager;
        }
    
        @TargetApi(Build.VERSION_CODES.LOLLIPOP)
        public String[] getListOfCameras() throws CameraAccessException {
            if (isTest) {
                return new String[0];
            }
    
            return cameraManager.getCameraIdList();
        }
    
        public void setTestMode() {
            isTest = true;
        }
    }
    

    Then your activity is gonna look like:

        public class MainActivity extends AppCompatActivity  {
    
        ActivityCameraManager activityCameraManager;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            ...
            activityCameraManager = new ActivityCameraManager((CameraManager) getSystemService(Context.CAMERA_SERVICE));
        }
    
        public void setActivityCameraManagerForTest() {
            activityCameraManager.setTestMode();
        }
    }
    

    And in test just call getActivity().setActivityCameraManagerForTest();

I hope, it helps

Konstantin Loginov
  • 15,802
  • 5
  • 58
  • 95
1

The answer is late but here is a mock camera example for Android.

You can set the VideoFileInputSource to mock camera from video file

textureVideoInputSource = new VideoFileInputSource(this, "mock_input_video.mp4");

or you can enable hardware camera for video stream.

textureVideoInputSource = new CameraTextureVideoInputSource(this);

You can refer the answer here. https://stackoverflow.com/a/38456189/1053097

Community
  • 1
  • 1
muneikh
  • 2,067
  • 5
  • 25
  • 59
  • Does this work with static image? I tried a png file and it did not work. – fangmobile Nov 17 '16 at 21:41
  • You can convert bitmap into texture and load on to GLSurfaceView. – muneikh Nov 17 '16 at 21:56
  • Thanks. Should I get the texture, load it in CameraActivity.java, where I change this.videoRenderer = new VideoRenderer(CameraActivity.this, this.textureVideoInputSource); to this.videoRenderer = new VideoRenderer(CameraActivity.this, this.texture); Assuming I have a private class member called text. I know this does not seem to be right. – fangmobile Nov 17 '16 at 22:34