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)
}
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.
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
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