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
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);
}
I have been having quite a bit of trouble implementing unit testing on the Android. As a simple test, I've been trying to match a string retrieved from string resources:
String myString = myActivity.getResources().getString(R.string.testString));
However, when unit testing this invariably results in a null pointer exception. This includes robolectric as well as the Junit implementation delivered with the Android sdk.
One possible solution is to approach the retrieval of resources in a manner similar to a data access object. That is, create an interface through which string resources would be accessed. This would allow me to mock access the string resource. Similarly, I could separate the non-android dependent behavior of, say, an Activity, into a separate pojo class. This would allow me to run unit tests using standard Java testing tools. In fact, I could potentially delegate any Android infrastructure related activity to an interface.
This seems like a lot of jumping through hoops to get to unit testing. Is it worth it? Is there a more viable approach?
It turned out, the problem was that the activity has to be gotten in the actual test method. So, for example, my method now looks like this:
public void testGetActivityResourceString() {
Activity myActivity = this.getActivity();
String myString = myActivity.getResources().getString(R.string.hello);
Assert.assertNotNull(myString);
}
Whereas before I was creating activity in setup. This giveaway was in the docs:
"For each test method invocation, the Activity will not actually be created until the first time this method is called."
This was a real hassle to figure out. The example for HelloWorldTest doesn't work for the same reason.
Here's the full entry:
Public T getActivity ()
Since: API Level 3
Get the Activity under test, starting it if necessary.
For each test method invocation, the Activity will not actually be created until the first time this method is called.
If you wish to provide custom setup values to your Activity, you may call setActivityIntent(Intent) and/or setActivityInitialTouchMode(boolean) before your first call to getActivity(). Calling them after your Activity has started will have no effect.
NOTE: Activities under test may not be started from within the UI thread. If your test method is annotated with UiThreadTest, then your Activity will be started automatically just before your test method is run. You still call this method in order to get the Activity under test.
This works correctly:
public void testGetResourceString() {
assertNotNull(mActivity.getResources()
.getString(com.example.pkg.R.string.testString));
}
Because you haven't provided any of your code but only the getReousrces() line, I will guess what you are doing wrong:
you are not using the correct base class for your test, use ActivityInstrumentationTestCase2 because you need the system infrastructure
you are using the resources of your test project instead of your project under test, that's why in my example the id is com.example.pkg.R.string.testString.
There are several methods of unit testing in Android, what's the best one for testing a custom view I've written?
I'm currently testing it as part of my activity in an instrumentation test case, but I'd rather test just the view, isolated.
A simple solution for the lack of a View-focused TestCase implementation would be to create a simple Activity within your test project that includes your view. This will allow you to write tests against the view using a simple Activity. Information on Activity testing:
http://developer.android.com/reference/android/test/ActivityUnitTestCase.html
As mentioned in wikibooks:
unit testing is a method by which individual units of source code are tested to determine if they are fit for use.
So when you say you want to test your custom view, you can check various methods of your custom views like "onTouchEvent", "onDown", "onFling", "onLongPress", "onScroll", "onShowPress", "onSingleTapUp", "onDraw" and various others depending on your business logic. You can provide mock values and test it. I would suggest two methods of testing your custom view.
1) Monkey Testing
Monkey testing is random testing performed by automated testing tools.
G.D.S. Prasad on geekinterview.com
and:
A monkey test is a unit test that runs with no specific test in mind. The monkey in this case is the producer of any input. For example, a monkey test can enter random strings into text boxes to ensure handling of all possible user input or provide garbage files to check for loading routines that have blind faith in their data.
sridharrganesan on geekinterview.com
This is a black box testing technique and it can check your custom view in so many unique conditions that you will get astonished :) .
2) Unit Testing
2a) Use Robotium Unit Testing Framwork
Go to Robotium.org or http://code.google.com/p/robotium/ and download the example test project. Robotium is a really easy to use framework that makes testing of android applications easy and fast. I created it to make testing of advanced android applications possible with minimum effort. Its used in conjunction with ActivityInstrumentationTestCase2.
2b) Use Android Testing Framework
Here are the links to the reference:
http://developer.android.com/reference/android/test/ActivityInstrumentationTestCase2.html
and
http://developer.android.com/reference/android/test/ActivityUnitTestCase.html
For starters:
http://developer.android.com/guide/topics/testing/testing_android.html
According to one user : Aside from easily testing non platform
dependent logic I haven't found a
clever way to run tests, so far (at
least for me) any actual platform
logic testing is cumbersome. It's
almost non trivial anyway because I've
found differences in implementation
between the emulator and my actual
device and I hate to run a unit test
implementation on my device just to
remove the application afterwards.
My strategy has been: Try to be
concise and make the logic well
thought out and then test
implementation piece by piece (less
then desirable).
Also Stephen Ng provides good aproach for real Unit Test for Android projects solution: https://sites.google.com/site/androiddevtesting/
One user has made a screencast.
Here's a ScreenCast I made on how I got Unit Tests to work. Simple Unit
Tests and more complex unit tests that
depend on having a reference to
Context or Activity objects.
http://www.gubatron.com/blog/2010/05/02/how-to-do-unit-testing-on-android-with-eclipse/
Hope it helps you testing your custom view in all possible conditions :)
Comment (futlib) All your suggestions seem to involve testing the ACTIVITY, while I really want to test just the VIEW. I might want to use this view in other activities, so it doesn't make much sense for me to test it with a specific one. – futlib
Answer: To implement a custom view,
you will usually begin by providing
overrides for some of the standard
methods that the framework calls on
all views. For example "onDraw",
"onKeyDown(int, KeyEvent)",
"onKeyUp(int, KeyEvent)",
"onTrackballEvent(MotionEvent)" etc of
your custom view. So when you want to
do unit testing for your custom you'll
have to test these methods, and
provide mock values to it so that you
can test your custom view on all
possible cases. Testing these methods
doesn't mean that you are testing your
ACTIVITY, but it means testing your
custom view (methods/functions) which
is within an activity. Also you'll
have to put your custom view in an
Activity eventually for your target
users to experience it. Once
thoroughly tested , your custom view
can be placed in many projects and
many activities.
Here's a different suggestion which works fine in many cases: Assuming you are referencing your custom view from within a layout file, you can use an AndroidTestCase, inflate the view, and then perform tests against it in isolation. Here's some example code:
my_custom_layout.xml:
<?xml version="1.0" encoding="utf-8"?>
<de.mypackage.MyCustomView ...
MyCustomView.java:
public class MyCustomView extends LinearLayout {
public MyCustomView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setTitle(CharSequence title) {
((TextView) findViewById(R.id.mylayout_title_textView)).setText(title);
}
...
MyCustomViewTest.java:
public class MyCustomViewTest extends AndroidTestCase {
private MyCustomView customView;
#SuppressLint("InflateParams")
#Override
protected void setUp() throws Exception {
super.setUp();
customView = (MyCustomView) LayoutInflater.from(getContext())
.inflate(R.layout.my_custom_layout, null);
}
public void testSetTitle_SomeValue_TextViewHasValue() {
customView.setTitle("Some value");
TextView titleTextView = (TextView) valueSelection.findViewById(R.id.mylayout_title_textView);
assertEquals("Some value", titleTextView.getText().toString());
}
...
I struggled a lot to set up screenshot tests for my custom view.
Here is how I managed to do that and everything I learned in the process.
It may not be the most convenient method, but I put it here anyway.
And of course, screenshot testing is now a little bit easier in Jetpack Compose.
⚠ Caution #1
You can use JUnit 4 if you want. I'm using JUnit 5. Because JUnit 5 is built on Java 8 from the ground up, its instrumentation tests will only run on devices running Android 8.0 (API 26) or newer. Older phones/emulators will skip the execution of these tests completely, marking them as ignored.
If you want to run JUnit 5 tests on Android, refer to this answer for how to set it up.
⚠ Caution #2
The screenshot tests may not work on other devices even if they have the same screen DPI (they may not work at all on devices with different screen DPIs). For example, even when I use the same device in my local machine and on GitHub Actions to run the tests, they do not produce the same result (GitHub Actions assertions fail). So, I had to disable them on GitHub Actions.
If you want to disable the screenshot tests on GitHub Actions (or other CI), see this answer.
⚠ Caution #3
If you have resources in instrumented tests (in androidTest source set) and you want to reference their id, you should use them like this (note the package name followed by .test):
com.example.test.R.id.an_id
For example, if your package name is my.package.name then to access the layout file in src/androidTest/res/layout/my_layout.xml in your tests, you use my.package.name.test.R.layout.my_layout.
⚠ Caution #4
Since we are saving our test screenshots on the external storage of the device/emulator, we need to make sure that we have both WRITE_EXTERNAL_STORAGE permission added in the manifest and adb install options -g and -r configured in the build script. When running on Marshmallow+, we also need to have those permissions granted before running a test. -g is for granting permissions when installing the app (works on Marshmallow+ only) while -r is to allow reinstalling of the app.
These correspond to adb shell pm install options.
Just be aware that this does not work with Android Studio yet.
So, create an AndroidManifest.xml file in src/androidTest/ directory and add the following to it:
<manifest package="my.package.name">
<!-- For saving screenshots in tests -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage"
tools:remove="android:maxSdkVersion"/>
<application android:requestLegacyExternalStorage="true">
<activity android:name=".MyActivityThatContainsTheView"/>
</application>
</manifest>
and add the adb install options in your library Gradle build file:
android {
// Note that adbOptions block is deprecated in Android Gradle Plugin 7.0.0;
// replace adbOptions block with installation block
adbOptions {
installOptions("-g", "-r")
}
}
⚠ Caution #5
I save the reference screenshot (the one I want to compare with the current screenshot) in src/androidTest/assets directory. So, specify that directory as an assets entry in the library build file:
android {
sourceSets {
// This is Kotlin DSL; see https://stackoverflow.com/a/59920318 for groovy DSL
get("debug").assets.srcDirs("src/androidTest/assets")
}
⚠ Caution #6
To pass instrumentation arguments when running the tests (like shouldSave in my code), do this:
For a Gradle task:
Running the task from command line: pass your arguments after the task name
./gradlew myTask -Pandroid.testInstrumentationRunnerArguments.shouldSave=true
Running the task with Studio: pass your arguments in run config Arguments: field
-Pandroid.testInstrumentationRunnerArguments.shouldSave=true
For an Android Studio Android Instrumented Tests run configuration:
Select Edit Configurations... from run configuration popup, then select your run configuration, click ... in front of Instrumentation arguments: field and then add a name-value entry like Name shouldSave Value true.
See this article and this post.
⚠ Caution #7
The first time you want to run the screenshot tests and also whenever you update your custom view that might change its visuals, you should run the tests passing true for shouldSave argument so the new screenshots are saved in the device (see comments above save method in code below for the location of the images) and then manually copy the new screenshots to your src/androidTest/assets/ directory so they will be the new reference ones.
⚠ Caution #8
Make sure to use -ktx versions of androidx libraries (like AndroidX Core library) for Kotlin.
The -ktx variants contain useful Kotlin extension functions. Example:
implementation("androidx.core:core-ktx:1.6.0")
⚠ Caution #9
Make sure the device screen is on and unlocked for the activity to go to resumed state.
The code
This is my test activity in src/androidTest/java/com/example/ directory that exposes the view that I want to take its screenshot as a property:
class MyActivityThatContainsTheView : AppCompatActivity() {
lateinit var myView: MyView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(my.package.name.test.R.layout.my_layout_that_contains_the_view)
myView = findViewById(my.package.name.test.R.id.my_view_id_in_the_layout_file)
}
}
And finally, this is my tests and how I save, load, and compare the screenshots:
#DisabledIfBuildConfigValue(named = "CI", matches = "true")
class ScreenshotTestView {
#JvmField
#RegisterExtension
val scenarioExtension = ActivityScenarioExtension.launch<MyActivityThatContainsTheView>()
lateinit var scenario: ActivityScenario<MyActivityThatContainsTheView>
// See ⚠ Caution #6 above in the post
val shouldSave = InstrumentationRegistry.getArguments().getString("shouldSave", "false").toBoolean()
val shouldAssert = InstrumentationRegistry.getArguments().getString("shouldAssert", "true").toBoolean()
#BeforeEach fun setUp() {
scenario = scenarioExtension.scenario
scenario.moveToState(Lifecycle.State.RESUMED)
}
#Test fun test1() {
val screenshotName = "screenshot-1"
scenario.onActivity { activity ->
val view = activity.myView
view.drawToBitmap()
.saveIfNeeded(shouldSave, screenshotName)
.assertIfNeeded(shouldAssert, screenshotName)
}
}
fun Bitmap.saveIfNeeded(shouldSave: Boolean, name: String): Bitmap {
if (shouldSave) save(name)
return this
}
fun Bitmap.assertIfNeeded(shouldCompare: Boolean, screenshotName: String) {
if (shouldCompare) assert(screenshotName)
}
/**
* The screenshots are saved in /Android/data/my.package.name.test/files/Pictures
* on the external storage of the device.
*/
private fun Bitmap.save(name: String) {
val context = InstrumentationRegistry.getInstrumentation().targetContext
val path = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)
val file = File(path, "$name.png")
file.outputStream().use { stream ->
compress(Bitmap.CompressFormat.PNG, 100, stream)
}
}
private fun Bitmap.assert(screenshotName: String) {
val reference = loadReferenceScreenshot(screenshotName)
// I'm using AssertJ library; you can simply use assertTrue(this.sameAs(reference))
assertThat(this.sameAs(reference))
.withFailMessage { "Screenshots are not the same: $screenshotName.png" }
.isTrue()
}
private fun loadReferenceScreenshot(name: String): Bitmap {
val context = InstrumentationRegistry.getInstrumentation().context
val assets = context.resources.assets
val reference = assets.open("$name.png").use { stream ->
BitmapFactory.decodeStream(stream)
}
return reference
}
}
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());
...
}
}