Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit testing a RecyclerView Adapter throws NullPointerException when accessing mObservables

My view model holds a very simple recyclerview adapter

When I try to send it messages (which in turn calls notifyDatasetChanged) it throws an exception like so

java.lang.NullPointerException
at androidx.recyclerview.widget.RecyclerView$AdapterDataObservable.notifyChanged(RecyclerView.java:11996)
at

the problem is that the mObservers variable from AdapterDataObservable is null

the thing is that this extends Observable<AdapterDataObserver> which in turn defines mObservers as

protected final ArrayList<T> mObservers = new ArrayList<T>();

so basically the moment my adapter is instantiated , it will call

private final AdapterDataObservable mObservable = new AdapterDataObservable();

(which is called by the way, mObservable is not null)

which in turn should call mObservers = new ArrayList<T>();

can someone explain why this is never called? or if there is a way to get past this problem?

as a side note the adapter is not mocked it is a solid object.

Edit:

Here is the code of the tests I'm using:

class LoginViewModelTest {

     private lateinit var vm: LoginViewModel

        @get:Rule
        val rule = InstantTaskExecutorRule()

        @Before
        fun setUp() {

            whenever(settings.hasShownWelcome).thenReturn(false)
            whenever(settings.serverIp).thenReturn("http://127.0.0.1")

            //this is where the crash happens
            vm = LoginViewModel(settings, service, app, TestLog, TestDispatchers) { p -> permissionGranted }
        }

And below is the code that is tested:

class LoginViewModel(private val settings: ISettings, private val service: AppService, application: Application, l: ILog, dispatchers: IDispatchers, val permissionChecker: (String) -> Boolean) :  BaseViewModel(application, l, dispatchers)

    val stepAdapter :StepAdapter

    init {
        val maxSteps = calculateSteps()
        //after this assignment, during the normal run, the stepAdapter.mObservable.mObservers is an empty array
        //during unit tests, after this assignment it is null
        stepAdapter = StepAdapter(maxSteps) 
    }
like image 299
Cruces Avatar asked Oct 01 '19 08:10

Cruces


2 Answers

I fixed mine by spying on the adapter and stubbing notifyDataSetChanged.

    val spyAdapter = spyk(adapter)
    every { spyAdapter.notifyDataSetChanged() } returns Unit
    spyAdapter.changeItems(items)
    verify { spyAdapter.notifyDataSetChanged() }

Please, note that changeItems calls notifyDataSetChanged internally.

like image 55
X09 Avatar answered Nov 14 '22 22:11

X09


i don't know if you have already found a solution or not, but this is for other people like me who ran into a similar problem:

make the test an android test (aka instrumented test) and not a unit test.

while i cannot fully explain why, it seems that when notifying an adapter about a change (notifyItemChanged(), notifyDataSetChanged() etc.) something about the inner logic of android requires an actual RecyclerView/adapter to receive the message.

once i moved my test from the Test folder to the AndroidTest folder, the problem was fixed.

P.S.

make sure to remove your old build configuration! android studio keeps referring to the old one (in the Test folder) and if you don't remove it you will receive a classNotFound error

like image 44
or_dvir Avatar answered Nov 14 '22 23:11

or_dvir