How do you manage to write unit tests that interact with the system classes i.e. Android Framework classes?
Imagine you have those classes:
public class DeviceInfo {
public final int screenWidth, screenHeight;
public final String model;
public DeviceInfo(int screenWidth, int screenHeight, String deviceModel) {
this.screenWidth = screenWidth;
this.screenHeight = screenHeight;
this.model = deviceModel;
}
}
public class DeviceInfoProvider {
private final Context context;
public DeviceInfoProvider(Context context) {
this.context = context;
}
public DeviceInfo getScreenParams() {
DisplayMetrics metrics = new DisplayMetrics();
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
windowManager.getDefaultDisplay().getMetrics(metrics);
int screenWidth = metrics.widthPixels;
int screenHeight = metrics.heightPixels;
String model= Build.MODEL;
DeviceInfo params = new DeviceInfo(screenWidth, screenHeight, model);
return params;
}
}
How can I write a test to verify the correct behaviour of the method DeviceInfoProvider.getScreenParams()
.
The following test passes, but it is very ugly and fragile:
@Test
public void testGetScreenParams() throws Exception {
// Setup
Context context = spy(RuntimeEnvironment.application);
DeviceInfoProvider deviceInfoProvider = new DeviceInfoProvider(context);
// Stub
WindowManager mockWindowManager = mock(WindowManager.class);
Display mockDisplay = mock(Display.class);
when(context.getSystemService(Context.WINDOW_SERVICE)).thenReturn(mockWindowManager);
when(mockWindowManager.getDefaultDisplay()).thenReturn(mockDisplay);
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
DisplayMetrics metrics = (DisplayMetrics) invocation.getArguments()[0];
metrics.scaledDensity = 3.25f;
metrics.widthPixels = 1081;
metrics.heightPixels = 1921;
return null;
}
}).when(mockDisplay).getMetrics(any(DisplayMetrics.class));
// Run
DeviceInfo deviceInfo = deviceInfoProvider.getScreenParams();
// Verify
assertThat(deviceInfo.screenWidth, equalTo(1081));
assertThat(deviceInfo.screenHeight, equalTo(1921));
assertThat(deviceInfo.model, equalTo(Build.MODEL));
}
How would you improve that?
Note: Currently I'm using Robolectric, Mockito and PowerMock
The System Under Test is too tightly coupled to implementation concerns. Try to avoid mocking classes you don't own. Abstract code behind interfaces and delegate responsibility to whichever implementation happens to be behind the interface at run time.
public interface DisplayProvider {
public int widthPixels;
public int heightPixels;
}
public interface BuildProvider {
public string Model;
}
Refactor dependent class to rely on the abstractions and not concretions (implementation concerns).
public class DeviceInfoProvider {
private final DisplayProvider display;
private final BuildProvider build;
public DeviceInfoProvider(DisplayProvider display, BuildProvider build) {
this.display = display;
this.build = build;
}
public DeviceInfo getScreenParams() {
int screenWidth = display.widthPixels;
int screenHeight = display.heightPixels;
String model = build.Model;
DeviceInfo params = new DeviceInfo(screenWidth, screenHeight, model);
return params;
}
}
Unit test in isolation
@Test
public void testGetScreenParams() throws Exception {
// Arrange
DisplayProvider mockDisplay = mock(DisplayProvider.class);
BuildProvider mockBuild = mock(BuildProvider.class);
DeviceInfoProvider deviceInfoProvider = new DeviceInfoProvider(mockDisplay, mockBuild);
when(mockDisplay.widthPixels).thenReturn(1081);
when(mockDisplay.heightPixels).thenReturn(1921);
when(mockBuild.Model).thenReturn(Build.MODEL);
// Act
DeviceInfo deviceInfo = deviceInfoProvider.getScreenParams();
// Assert
assertThat(deviceInfo.screenWidth, equalTo(1081));
assertThat(deviceInfo.screenHeight, equalTo(1921));
assertThat(deviceInfo.model, equalTo(Build.MODEL));
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With