What's the difference between getTargetContext() and getContext (on InstrumentationRegistry)?
InstrumentationRegistry
is an exposed registry instance that holds a reference to the instrumentation running in the process and it's arguments and allows injection of the following instances:
InstrumentationRegistry.getInstrumentation()
, returns the Instrumentation currently running.InstrumentationRegistry.getContext()
, returns the Context of this Instrumentation’s package.InstrumentationRegistry.getTargetContext()
, returns the application Context of the target application.InstrumentationRegistry.getArguments()
, returns a copy of arguments Bundle that was passed to this Instrumentation. This is useful when you want to access the command line arguments passed to Instrumentation for your test.
EDIT:
So when to use getContext() vs getTargetContext()?
The documentation doesn't do a great job of explaining the differences so here it is from my POV:
You know that when you do instrumentation tests on Android then you have two apps:
- The test app, that executes your test logic and tests your "real" app
- The "real" app (that your users will see)
So when you are writing your tests and you want to load a resource of your real app, use getTargetContext()
.
If you want to use a resource of your test app (e.g. a test input for one of your tests) then call getContext()
.
You may need InstrumentationRegistry.getContext()
for access to raw resources of a test case.
E.g., to access app/src/androidTest/res/raw/resource_name.json
in a TestCase:
final Context context = InstrumentationRegistry.getContext();
InputStream is = context.getResources().openRawResource(com.example.package.test.R.raw.resource_name);
Took me hours to find that out.
A InstrumentedTest case had a context member which was set up in the setup like this:
context = InstrumentationRegistry.getTargetContext();
this was used to open files, especially things like that:
String filenameOriginal = context.getCacheDir() + "/initial.csv";
Now I decided that I need to use some resource which is only available in the instrumented test, I did not want to distribute this resource with the release version. Therefore I recreated the resource directories under test:
app\src\androidTest
└───res
└───raw
v1.csv
But to be able to use this in code I had to call something like this:
public static Uri resourceToUri(Context context, int resID)
{
return Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" +
context.getResources().getResourcePackageName(resID) + '/' +
context.getResources().getResourceTypeName(resID) + '/' +
context.getResources().getResourceEntryName(resID));
}
resourceToUri(context, R.raw.v1)
Which would always fail because R.raw.v1 coincidently corresponded to something that was actually in my R
resource file from the main application. By using resources in the instrumented tests, there were two R
files generated. To fix that I had to include the test R file:
import com.my_thing.app.my_app.test.R;
the resourceToUri
call would then however sill fail.
The issue was, that I must not have used InstrumentationRegistry.getTargetContext()
but ratherInstrumentationRegistry.getInstrumentation().getContext()
to get the resource of R.raw.v1
so I blindly replace the setup of the context for the whole test class to
context = InstrumentationRegistry.getInstrumentation().getContext();
It worked well for the specific test but other tests in the test case started to fail with permission denied for the filenameOriginal
I was using above.
Turned out that by replacing the context to the instrumentation context I obtained a path to which my app had no access and I got FileNotFoundException
with permission denied
and no GrantTestRule
or other things would work.
So be careful when using those contexts, it might screw your time :/