7

i am just developing an Android Application (API 15) and got the following Problem, when trying to write a unit test:

I use android.os.Parcel for saving a class (e.g. if the screen is turned around) and send it to another Activity. If i try to unit test the Parcel Method like this (i got this from the internet):

@Test
public void testParcel() {
    Comment test = new Comment();
    test.setId(testNr0Id);
    test.setComment(testNr0String);


    // Obtain a Parcel object and write the parcelable object to it:
    Parcel parcel = Parcel.obtain();
    test.writeToParcel(parcel, 0);

    // After you're done with writing, you need to reset the parcel for reading:
    parcel.setDataPosition(0);

    // Reconstruct object from parcel and asserts:
    Comment createdFromParcel = Comment.CREATOR.createFromParcel(parcel);
    assertEquals(test, createdFromParcel);
}

then i get a NullPointerException, because the Parcel returned from Parcel.obtain() is null. The JavaDoc is just saying that this Method "returns an parcel Object from the pool". Now my question is: Which pool? And why is this Parcel-Object null? Any suggestions how to make this test run?

Thanks for your help

Michael

3 Answers3

14

I've had the same issue. However after I moved the test from test directory to androidTest directory everything started working fine. I think this is due to the fact that Parcel class is system one, so it needs to be instrumented as well.

Mikhail
  • 3,666
  • 4
  • 30
  • 43
  • 3
    You might not want to move this to Android tests - it would require having devices attached or using Robolectric (or similar tools) which creates a big test dependency you might not like introducing for this one use-case only. So, if you want a JUnit-only solution, you probably want to fake the Parcel implementation on your own and use that in your tests - https://stackoverflow.com/a/50634063/2102748 – milosmns May 31 '18 at 23:15
  • 1
    Not saying that there is anything wrong with doing it through Android tests with Robolectric, but a simple JUnit solution might suffice for some (my case as well) :) – milosmns May 31 '18 at 23:16
5

To avoid going to Robolectric or instrumentation altogether, e.g. you want to use JUnit only with no devices attached, you can use Mockito to mock everything inside of the Parcel.

I have a simple implementation and I use it across many of my projects:

// Mockito is required for this to work
class ParcelFake {

  companion object {
    @JvmStatic fun obtain(): Parcel {
      return ParcelFake().mock
    }
  }

  private var position = 0
  private var store = mutableListOf<Any>()
  private var mock = mock<Parcel>()

  init {
    setupWrites()
    setupReads()
    setupOthers()
  }

  // uncomment when needed for the first time
  private fun setupWrites() {
    val answer = { i: InvocationOnMock ->
      with(store) {
        add(i.arguments[0])
        get(lastIndex)
      }
    }
    whenever(mock.writeByte(anyByte())).thenAnswer(answer)
    whenever(mock.writeInt(anyInt())).thenAnswer(answer)
    whenever(mock.writeString(anyString())).thenAnswer(answer)
    // whenever(mock.writeLong(anyLong())).thenAnswer(answer)
    // whenever(mock.writeFloat(anyFloat())).thenAnswer(answer)
    // whenever(mock.writeDouble(anyDouble())).thenAnswer(answer)
  }

  // uncomment when needed for the first time
  private fun setupReads() {
    val answer = { _: InvocationOnMock -> store[position++] }
    whenever(mock.readByte()).thenAnswer(answer)
    whenever(mock.readInt()).thenAnswer(answer)
    whenever(mock.readString()).thenAnswer(answer)
    // whenever(mock.readLong()).thenAnswer(answer)
    // whenever(mock.readFloat()).thenAnswer(answer)
    // whenever(mock.readDouble()).thenAnswer(answer)
  }

  private fun setupOthers() {
    val answer = { i: InvocationOnMock ->
      position = i.arguments[0] as Int
      null
    }
    whenever(mock.setDataPosition(anyInt()))
      .thenAnswer(answer)
  }
}

The basic idea is to have a backing storage (i.e. a list) and push everything into it - but you do need to mock a Parcel internally using Mockito to get this to work. I commented out some data types, but I assume you'll get the idea.

This is a result of reading through docs and trying out various approaches. Any edits are welcome.

milosmns
  • 3,595
  • 4
  • 36
  • 48
0

Parcel is in the Android framework, so your test will only work using Expresso as Mikhail comments, or using Robolectric as the test runner.

Robolectric mocks the Android.jar and allow this kind of testing, but consider that you will be unit testing Android code...

Miguel Sesma
  • 750
  • 5
  • 15
  • 1
    Try newer version of Robolectric and see if it better supports Parcel, I saw this as improvement: https://github.com/robolectric/robolectric/blob/master/shadows/framework/src/main/java/org/robolectric/shadows/ShadowParcel.java – Chris Li May 25 '18 at 02:18