0

I am trying to write some Unit Tests to assert that the proper Cipher Suites are used for varying versions of the Android SDK in my app.

In order to mock the result of Build.VERSION.SDK_INT I am attempting to use the Field.set() call...

I have a utility method that looks like this (pulled from https://stackoverflow.com/a/40303593/1226095 and linked answers):

private static void mockSdkVersion(Field field, Object newValue) throws Exception {
    field.setAccessible(true);
    Field modifiersField = Field.class.getDeclaredField("modifiers");
    modifiersField.setAccessible(true);
    modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
    field.set(null, newValue);
}

and then I have several tests that utilize this like so:

 @Test
    public void testApprovedCipherListForApi16() throws Exception {
        // force SDK_INT to 16
        mockSdkVersion(Build.VERSION.class.getField("SDK_INT"), 16);
        // obtain the list of suites that should be returned for API 16
        ArrayList<String> approvedCipherSuites = ConnectionSpec.APPROVED_CIPHER_SUITES;
        // assert things...
    }

@Test
public void testApprovedCipherListForApi20() throws Exception {
    // force SDK_INT to 20
    mockSdkVersion(Build.VERSION.class.getField("SDK_INT"), 20);
    // obtain the list of suites that should be returned for API 20
    ArrayList<String> approvedCipherSuites = ConnectionSpec.APPROVED_CIPHER_SUITES;
    // assert things...
}

This works when I run the tests one at a time, but when I run them all at once (such as with a CI setup like Jenkins) it seems to be not resetting the value (it stays at 16), assumingly because it is Static.

Any thoughts on a) how I can get around this ? b) how I can alternatively mock this ?

jesses.co.tt
  • 2,689
  • 1
  • 30
  • 49
  • 2
    Why are the words "dependency injection" entering my head right now? – Joe C Jul 10 '17 at 21:21
  • I know... but this codebase is pretty untestable... working hard on trying to fix that... – jesses.co.tt Jul 10 '17 at 21:22
  • Besides, it's a static class I am calling, and I feel that I would just run into the same issue with not being able to Mock (PowerMock is not an option just yet...) – jesses.co.tt Jul 10 '17 at 21:25
  • 1
    Personally, I would be tempted to avoid unit testing static classes, as you clearly want to get rid of them anyway. I would, if possible, try to look at end-to-end integration tests, which should provide more valuable crutches to your refactoring efforts. – Joe C Jul 10 '17 at 21:28
  • That's a fair comment... However, I don't really know too much about Integration Tests (i'm still learning the who/what/where/when/why/how of Unit Tests)... And we have a top down mandate (and merge checks) to include Unit Tests for all new code... and this story - to update the Cipher Suites for Android 'O' - only edits the Static classes... – jesses.co.tt Jul 10 '17 at 21:31
  • I don't see a static class definition here. Only nested classes can be static. Please show the definition of the nested class and its containing class. – Lew Bloch Jul 10 '17 at 23:16
  • Well Build.VERSION.SDK_INT is static – jesses.co.tt Jul 10 '17 at 23:18

2 Answers2

1

You need to reset Build.VERSION after each test method call. Something like this:

private static final int DEFAULT_VERION = Build.VERSION.SDK_INT;

@After
public void after(){
    mockSdkVersion( Build.VERSION.class.getField("SDK_INT"), DEFAULT_VERION );
}
tsolakp
  • 5,858
  • 1
  • 22
  • 28
0

I found out that I can replicate the behaviour I am looking for by simply adding the @Config(sdk = Build.VERSION_CODES.XXX) annotation to the Test Methods (assuming that you are using RoboElectric

http://robolectric.org/configuring/

jesses.co.tt
  • 2,689
  • 1
  • 30
  • 49