How to access resources in Android Unittest? - android

My project has two projects:
main
mainTest
I created an "AndroidTestCase" in the "mainTest" and I tried to use getContext().getResources() to access the resources I created in the "mainTest" project. But I found the code is actually trying to get the resources in the "main" project. I think it is because when I use getContext() it returns an context representing the "main" project. But the AndroidTestCase class does not provide any methods to get its own resources.
P.S. I found I can directly use class.getResourceAsStream() to access the raw file, which is what I want. But still I hope there are more convenient way.

You can, from the application Context , request the resources from your test application.
protected Resources getResources(String packageName) throws NameNotFoundException {
PackageManager pm = getContext().getPackageManager();
return pm.getResourcesForApplication(packageName);
}

Related

Getting Resources$NotFoundException on espresso when trying to access test resources

I'm getting android.content.res.Resources$NotFoundException: String resource ID when trying to access resources defined in the androidTest/res during runtime on my tests. I've tried a couple of things and no luck. Isn't this supposed to work? My code sample:
ExampleInstrumentedTest.kt
#RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
#Test
fun test() {
val result = InstrumentationRegistry.getInstrumentation().targetContext.getString(
com.myproject.test.R.string.my_test
)
assertEquals("my test", result)
}
}
strings.xml
<resources>
<string name="my_test">my test</string>
</resources>
Has anyone faced this issue?
Found the solution for this. The issue at hand is that InstrumentationRegistry.getInstrumentation().targetContext will only be able to access what's included in the application being tested, so in this case I should use InstrumentationRegistry.getInstrumentation().context, which will be able to access test resources. For reference:
getTargetContext
Return a Context for the target application being instrumented. Note that this is often different than the Context of the instrumentation code, since the instrumentation code often lives is a different package than that of the application it is running against. See getContext() to retrieve a Context for the instrumentation code.
getContext
Return the Context of this instrumentation's package. Note that this is often different than the Context of the application being instrumentated, since the instrumentation code often lives is a different package than that of the application it is running against. See getTargetContext() to retrieve a Context for the target application.
from doc: https://developer.android.com/reference/android/app/Instrumentation

Android Studio: Cannot write to Shared Preferences in instrumented test

I'm trying to write a test case to verify a class that writes to Shared Preferences.
I'm using Android Studio v1.5.
In the good old eclipse, when using AndroidTestCase, a second apk file was deployed to the device, and tests could be run using the instrumentation context, so you could run tests using the instrumentation apk's shared preferences without altering the main apk's existing shared preferences files.
I've spent the entire morning trying to figure out how to get a non null context in Android Studio tests. Apparently unit tests made for eclipse are not compatible with the Android Studio testing framework, as calling getContext() returns null.
I thought I've found the answer in this question:
Get context of test project in Android junit test case
Things have changed over time as old versions of Android Studio didn't have full testing support. So a lot of answers are just hacks. Apparently now instead of extending InstrumentationTestCase or AndroidTestCase you should write your tests like this:
#RunWith(AndroidJUnit4.class)
public class MyTest {
#Test
public void testFoo(){
Context instrumentationContext = InstrumentationRegistry.getContext();
Context mainProjectContext = InstrumentationRegistry.getTargetContext();
}
}
So I now have a non null instrumentation context, and the getSharedPreferences method returns an instance that seems to work, but actually no preferences file is being written.
If I do:
context = InstrumentationRegistry.getContext();
Then the SharedPreferences editor writes and commits correctly and no exception is thrown. On closer inspection I can see that the editor is trying to write to this file:
data/data/<package>.test/shared_prefs/PREFS_FILE_NAME.xml
But the file is never created nor written to.
However using this:
context = InstrumentationRegistry.getTargetContext();
the editor works correctly and the preferences are written to this file:
/data/data/<package>/shared_prefs/PREFS_FILE_NAME.xml
The preferences are instantiated in private mode:
SharedPreferences sharedPreferences = context.getSharedPreferences(fileName, Context.MODE_PRIVATE);
As far as I know, no test apk has been uploaded to the device after running the test. This might explain why the file was not written using the instrumentation context. Is it possible that this context is a fake context that fails silently?
And if this were the case, how could I obtain a REAL instrumentation context so that I can write preferences without altering the main project's preferences?
Turns out you can't write to shared preferences using the instrumentation context, and this was true even in eclipse. This would be the equivalent test for eclipse:
import android.content.Context;
import android.content.SharedPreferences;
import android.test.InstrumentationTestCase;
public class SharedPrefsTest extends InstrumentationTestCase {
public void test() throws Exception {
Context context = getInstrumentation().getContext();
String fileName = "FILE_NAME";
SharedPreferences sharedPreferences = context.getSharedPreferences(fileName, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString("key", "value");
editor.commit();
SharedPreferences sharedPreferences2 = context.getSharedPreferences(fileName, Context.MODE_PRIVATE);
assertEquals("value", sharedPreferences2.getString("key", null));
}
}
I just ran it and it also fails. The preferences are never written. I think internal storage file access is forbidden in this context, as calling Context.getFilesDir() throws an InvocationTargetException, and so does calling File.exists() over the preferences file (you can check which file is the editor writing to using the debugger, just look for a private variable called mFile inside the this.$0 member instance).
So I was wrong in thinking this was actually possible. I though we had used the instrumentation context for data access layer testing in the past, but we actually used the main context (AndroidTestCase.getContext()), although we used different names for the preferences and SQLite files. And this is why the unit tests didn't modify the regular app files.
The instrumentation will be installed alongside with your application. The application will run itself, thus reading and writing its own SharedPreferences.
It is odd, that the SharedPreferences of the Instrumentation get deleted (or never created), but even if they would be created, you would have a hard time passing them into your application under test. As stated above just calling context.getSharedPreferences(); inside your app will still provide the actual apps preferences, never the ones of your instrumentation.
You will need to find a way to provide the preferences to your application under test. A good solution to this would be to keep the preferences in your Application like the following:
public class App extends Application {
SharedPreferences mPreferences;
public void onCreate() {
mPreferences = getSharedPreferences(fileName, Context.MODE_PRIVATE);
}
// add public getter / setter
}
This way, you can
As long as you have a context get the preferences from a single source using ((App) context.getApplicationContext()).getPreferences()
Set the preferences yourself before running your tests and starting any activities to inject your test data.
In your test setup then call the following to inject any preferences you need
#Before
public void before() {
((App) InstrumentationRegistry.getTargetContext()).setPreferences(testPreferences);
}
Be sure to correctly finish off activities after each test, so that each test can get their own dependencies.
Also, you should strongly think about just mocking the SharedPreferences using Mockito, other frameworks, or simply implementing the SharedPreferences interface yourself, since this greatly simplifies verifying interactions with models.
Note that you can still use Android Studio to run your old-style tests which extend InstrumentationTestCase, AndroidTestCase, or ActivityInstrumentationTestCase2 in Android Studio.
The new Testing Support Library provides ActivityTestRule to provide functional testing of a single activity. I believe you need to create one of these objects before attempting to get the Instrumentation and/or Context used for the test. The documentation for Espresso has an example for using this class.
If you want to test classes without having to create an Activity, I found it's easiest to use Robolectric. Robolectric provides you with a mock context that does everything a context does. In fact, this context is the main reason I use Robolectric for unit testing.

Why use ContextWrapper directly in an Activity instead of implicit context from "this"

Going through some supposedly "good" sources sources to learn the details and tricks of context handling in Android I have come across one pattern multiple time that I fail to understand.
What is the advantage of using a ContextWrapper when you could equally well use the implicit context?
For example why use the following in an activity method (defined directly in an Activity class)
...
ContextWrapper cw = new ContextWrapper(getApplicationContext())
File filesDir = cw.getFilesDir();
...
Instead of just
...
File filesDir = getFilesDir();
...
even though getFilesDir() is defined in the ContextWrapper class the Activity is anyway a subclass of ContextWrapper so you have direct access to the method anyway.
So what potential issue (that I fail to see) does this added complexity address?
I'd say (and I might be wrong) that in the scenario (and context) you presented might not make a difference. getApplicationContext().getFilesDir() could have been used just as easily.
However, I believe ContextWrapper might be useful in other scenarios. From what I understand, this is the adapter pattern. You may want to provide different behaviour only for certain methods while proxying all other to the original context reference you pass in.
Check out this piece of code from RemoteViews:
// RemoteViews may be built by an application installed in another
// user. So build a context that loads resources from that user but
// still returns the current users userId so settings like data / time formats
// are loaded without requiring cross user persmissions.
final Context contextForResources = getContextForResources(context);
Context inflationContext = new ContextWrapper(context) {
#Override
public Resources getResources() {
return contextForResources.getResources();
}
#Override
public Resources.Theme getTheme() {
return contextForResources.getTheme();
}
};

Android JUnit4 Testing - Where to get Context from?

I have to build an app with sqlite usage. Now I want to write my unit tests. These unit tests should test my class SQLiteBridge. SQLiteBridge provides DAOs for every child class of Model.
Now I got the problem that I need a context to create my SQLiteBridge. SQLiteBridge creates and handles a SQLite database on the system..
Where to get the Context-Object from?
My setup is like here (so I'm using Junit4 [thanks god]):
http://tools.android.com/tech-docs/unit-testing-support
EDIT: I hope there is a way like the old AndroidTestCase to extend without losing Junit4. :)
As described here: https://code.google.com/p/android-test-kit/wiki/AndroidJUnitRunnerUserGuide
Use the InstrumentationRegistry to obtain the context.
However if you call InstrumentationRegistry.getContext() directly you may get an exception opening your database. I believe this is because the context returned by getContext() points to the instrumentation's context rather than that of your application / unit test. Instead use InstrumentationRegistry.getInstrumentation().getTargetContext()
For example:
#RunWith(AndroidJUnit4.class)
public class SqliteTest {
Context mMockContext;
#Before
public void setUp() {
mMockContext = new RenamingDelegatingContext(InstrumentationRegistry.getTargetContext(), "test_");
}
}
The RenamingDelegatingContext simply prefixes the file/database names with test_ to prevent you from overwriting data that you may have in the same simulator.
jUnit 4 (and perhaps other versions of jUnit) and androidx use:
ApplicationProvider.getApplicationContext();
See: Android Documentation

IsolatedContext vs. AndroidTestCase.getContext()

I'm writing some tests to test my sqllite database code. Can someone here explain if there would be a difference writing those tests using the context I get from AndroidTestCase.getContext() or using an IsolatedContext.
For those that don't want to follow the link to the Google Group, here is the answer given there:
AndroidTestCase.getContext() returns a normal Context object. It's the
Context of the test case, not the component under test.
IsolatedContext returns a "mock" Context. I put "mock" in quotes
because its not a mock in the normal sense of that term (for testing).
Instead, it's a template Context that you have to set up yourself. It
"isolates" you from the running Android system, so that your Context
or your test doesn't accidentally get outside of the test fixture. For
example, an IsolatedContext won't accidentally hit a production
database (unless you set it up to do that!) Note, however, that some
of the methods in an IsolatedContext may throw exceptions.
IsolatedContext is documented in the Developer Guide under Framework
Topics > Testing, both in Testing Fundamentals and in Content Provider
Testing.
Here is the Android docs on IsolatedContext.
And here is the relevant section of the Testing Fundamentals document.
The answer:
http://groups.google.com/group/android-developers/browse_thread/thread/3a7bbc78258a194a?tvc=2
I had the simple problem: I need to test my DAO class without touching the real database. So I found the IsolatedContext from docs. But finally I found the other context in the same docs: RenamingDelegatingContext might be more easier to use. Here is my test case:
public class AddictionDAOTest extends AndroidTestCase {
#Override
public void setUp() throws Exception {
super.setUp();
setContext(new RenamingDelegatingContext(getContext(), "test_"));
}
public void testReadAllAddictions() throws Exception {
ImQuitDAO imQuitDAO = new ImQuitDAO(getContext());
...
}
}

Categories

Resources