robolectric / robolectric

Android Unit Testing Framework
http://robolectric.org
Other
5.82k stars 1.36k forks source link

Add an explanation of why using mocking frameworks to mock Android classes like Activity/Context/etc. is a bad idea #9007

Open bacecek opened 2 months ago

bacecek commented 2 months ago

I found the following recommendation in various issues about using mocking frameworks for mocking Android classes like Activity/Context/etc.:

You should never mock Context objects using mocking frameworks. You should only be using Robolectric APIs to customize the behavior of Android classes. At Google mocking objects like Context, Activity, etc.. are banned.

Unfortunatelly, I've not found this recommendation in Android open source projects: https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:docs/testing.md https://cs.android.com/android/platform/superproject/main or this robolectric repo.

Moreover, when any Android related class is used in test without Robolectric runner, the following exception fails the test:

Caused by: java.lang.ExceptionInInitializerError: Exception java.lang.RuntimeException: Method isEmpty in android.text.TextUtils not mocked. See https://developer.android.com/r/studio-ui/build/not-mocked for details. [in thread "Test worker"]
    at android.text.TextUtils.isEmpty(TextUtils.java)
    ...

which points to the page https://developer.android.com/training/testing/local-tests#mocking-dependencies, where example shows that mocking Context is okay.

Could someone explain why mocking Android classes is the a bad idea and why?

utzcoz commented 2 months ago

@bacecek It's not recommended using mock with Robolectric together. You can only use mock for one test file, for example mocking Activity, Context by yourself. You can only use Robolectric for your one test file by leveraging Robolectric's fake implementations of Android Frameworks. But it's not allowed/recommended to use them together as they use similar mechanism to modify class not and generate extra code for mocking purpose or shadow purpose, and sometimes they have conflicted with each other.

hoisie commented 2 months ago

cc @charlesmunger

It is generally OK to mock Android objects, but there are a certain set of them that I would not recommend mocking. They are banned from being mocked internally at Google. These are the very large, complex classes like Context, Fragment, Resources, View, Activity, Application, etc..

Let's take Context for example. It's a very very complex class with hundreds of methods, many of which are @hide API, so these hidden methods are only invoked by other classes in the Android framework.

There are a lot of Android classes that take Context as a parameter. One I dealt with today was ViewConfiguration.get(Context), which builds a ViewConfiguration given the Context.

Let's say you are using a mock Context when calling ViewConfiguration.get(Context). You would have to implement:

Now let's say that the new Version of Android introduced some new properties in ViewConfiguration, and some of those are derived from other methods in Context. You will now have to update the Context mock to try to return sensible values for those.

So you will typically feel the pain when you update the SDK level used in Robolectric tests.

Perhaps for a small development team this would not be a large burden. But at scale, for a company like Google, multiply these issues by hundreds of apps and thousands of developers, and updating the Android SDK becomes a very large challenge.

If people standardize on using Context objects built in Robolectric, there is a single place that needs to be changed if there are incompatibilities.