I have moved from the implementation stage of my application to the testing phase. I am trying to write some unit tests but struggling with the concept. I have been able to set up and run the sample unit test in android studio, which passed.
It is a simple test that the result of 2+2 is equal to 4. This I understand, the issue is how do I write a unit test to test a more complex method of my application?
Take for example the method below which is used to register a user to a database using JSON. Obviously this method isnt as easy as 2+2, so how would one write a unit test for this?
I am also having issues with a simple test case that checks if an email is valid or not, the error I am getting is cannot resolve method 'is(boolean)'
valid email test
public class UnitTest {
#Test
public void validEmailTest() throws Exception {
assertThat(RegisterActivity.isValidEmail("rory#gmail.com"), is(true));
}
}
Unit test Class
import com.example.test.Activities.RegisterActivity;
import org.junit.Test;
import static org.junit.Assert.*;
public class UnitTest {
#Test
public void addition_isCorrect() throws Exception {
assertEquals(4, 2 + 2);
}
#Test
public void validEmailTest() throws Exception {
assertThat(RegisterActivity.isValidEmail("rory#gmail.com"), is(true));
}
}
Take a look at Getting Started with Testing.
You should pay special attention to Instrumented tests, which are tests that run on an Android device or emulator and thus they have access to internal information of the application under test. Instrumented tests can be used for unit, user interface (UI), or app component integration testing.
You can find examples at https://github.com/googlesamples/android-testing
Related
The follows was the code which I want to test.
public class Demo {
private static final List<Pair<String, String>> mList;
static {
mList = new ArrayList<>();
mList.add(new Pair<>("F0", "T1"));
mList.add(new Pair<>("F1", "T2"));
mList.add(new Pair<>("F2", "T3"));
}
public String getStr(int pos) {
return mList.get(pos).first;
}
}
I was an android developer. I have get some trouble in test and mock the code.I have use mockito.
I have try some code to test it,but the result was not my expect.
1.First try
#Test
public void test(){
Demo demo=new Demo();
assertEquals(demo.getStr(0),"F0");
/**
* java.lang.AssertionError:
* Expected :null
* Actual :F0
*/
}
2.Second try
#Test
public void test() {
Demo demo = mock(Demo.class);
doCallRealMethod().when(demo).getStr(0);
assertEquals(demo.getStr(0), "F0");
/**
* java.lang.AssertionError:
* Expected :null
* Actual :F0
*/
}
Anyone tell me how can I resolve this problem to make demo.getStr(0) == "F0" by call the real method? Thanks!
===========================
Another question relate to it
I have try an another test to test android.util.Pair class, and the result is that "pair.first" was null,.(There are androidTest and test directory,I put it into test package.Did it impact the result?)
import android.util.Pair;
import org.junit.Test;
import org.mockito.Mockito;
import static org.junit.Assert.assertEquals;
public class DemoTest {
#Test
public void test1(){
Pair<String,String> pair=new Pair("First","Second");
assertEquals("First",pair.first);
//pair.first was null,why?
}
#Test
public void test2(){
Pair<String,String> pair= Mockito.spy(Pair.class);
assertEquals("First",pair.first);
//pair.first was null also,why?
}
}
Why the simple code is correct in real android environment,but failure in test?
I had the same problem too. month ago I have problem with TextUtils class too.
I report this to jUnit but they told me the problem is with android package because in unit test environment you don't have access to platform specific classes
for that pair case you can use this package. this works for me
import android.support.v4.util.Pair;
The problem in your first try is, that the public field "first" is actually null.
Is the Pair class the one from the "javafx.util" package or a custom implementation?
Did you forget "this.first = first" or something similar in the constructor of the "Pair" class?
I would also recommend to change the following line:
assertEquals(demo.getStr(0),"F0");
to
assertEquals("F0", demo.getStr(0));
so that the error is printed correctly.
Your second try does not make any sense. What is the point in mocking the class you want to test?
I think the second example has the same problem as the first one. Pair.first is never set. If you fix that, it should also work (untested).
From Google's Android tools website:
"Method ... not mocked."
The android.jar file that is used to run unit tests does not contain any actual code - that is provided by the Android system image on real devices. Instead, all methods throw exceptions (by default). This is to make sure your unit tests only test your code and do not depend on any particular behaviour of the Android platform (that you have not explicitly mocked e.g. using Mockito).
So how can we solve this?
In other words. If you need a default android class to work properly you either have to include it from a separate repository, or implement it yourself.
In the case of Android's Pair class. You can use android.support.v4.util.Pair instead.
To get access to this class, you can include com.android.support:support-compat:27.0.0 in your build.gradle or dependencies file.
If you are not using Gradle, you can copy the implementation of this file and use it in place of the official one. Or you can try and download the .jar file from this older version https://mvnrepository.com/artifact/com.google.android/support-v4/r7 (I have not tested whether it works)
Another approach (based on this) is to create the class in app/src/test/java/android/util/Pair.java and copy the code from the Android implementation.
This way you don't need extra dependencies. (There may be issues related to the implementation changing after you make the copy, but the dependencies may become stale as well.)
I'm starting using Device Farm. My problem is that it is ignoring the order that I used to write tests. In local all works fine because Espresso executes tests in a certain order. To declare that order I used alphabetical order. All my classes starts with a letter (E.g. "A_EspressoTest") so I can choose which class has to be ran first.
Into my classes I use
#FixMethodOrder(MethodSorters.NAME_ASCENDING)
to declare in which order my tests have to be ran.
It seems also like Device Farm ignores all my annotations (E.g. "#Test" ) because it is running also methods that doesn't have that annotation.
Lorenzo,
As of today, there is no way to specify the order of test execution with Espresso. Additionally, your observation about the #Test annotation is correct, we don't currently use that when discovering what test classes/test methods are selected.
AWS Device Farm currently discovers tests based on JUnit 3 style naming conventions (classes starting/ending with the word Test and methods within those classes starting with the word test.
For example:
// This class would be automatically discovered (by name).
public class LoginTests extends InstrumentationTestCase {
// This method would be automatically discovered (by name).
public void testLoginFormUsingInvalidCredentials() {
// ...
}
// This method would **not** be automatically discovered.
#Test
public void loginWithValidCredentials() {
// ...
}
}
// This class would **not** be automatically discovered.
public class Login extends InstrumentationTestCase {
// This method would **not** be automatically discovered since the class was not discovered.
public void testLoginFormWithValidCredentials() {
// ...
}
}
With all that said, we've heard plenty of feedback and requests for supporting test discovery using all JUnit 4 annotations and it's an area of improvement we're definitely taking a look at.
Hope that helps!
Best,
Andrew # AWS Device Farm
I've been trying to cover my Android app with tests and have started using espresso recently. Pretty impressed with it so far. However most of my app's functionality requires that users are logged in. And since all tests are independent, this requires registering a new user for each test. This works fine however the time required for each test increases considerably because of this.
I am trying to find a way to register a user once in a class (of tests) and then use that same user account to perform all the tests in that class.
One way I have been able to do this is to actually have only one test (#Test) method that runs all the other tests in the order I want. However this is an all or nothing approach, since the gradle cAT task only outputs the results once at the end without providing info about the intermediate tests that may have passed/failed.
I also tried the #BeforeClass approach which however did not work (no gradle output from the class where I had used this even with the debug option and it seemed like it took a long time before it moved on to the next class of tests).
Is there a better approach to register a user once at start of a class and then logout once at the end of testing?
Any help appreciated.
Ideally you would test the login/logout functionality in a set of tests that just test different login/logout scenarios, and let the other tests focus on other use cases. However, since the other scenarios depend on the user being logged in, it sounds like one way to solve this would be to provide a mock version of the app component handling the login. For the other login dependent tests, you would inject this mock at the start and it would return mock user credentials that the rest of the app can work with.
Here's an example where Dagger, Mockito and Espresso is being used to accomplish this: https://engineering.circle.com/instrumentation-testing-with-dagger-mockito-and-espresso-f07b5f62a85b
I test an app that requires this same scenario. The easiest way I've gotten around this is to split up logging in and out into their own test classes. Then you add all your test classes to a suite, starting and ending with the login and logout suites respectively. Your test suites ends up looking kind of like this.
#RunWith(Suite.class)
#Suite.SuiteClasses({
LoginSetup.class,
SmokeTests.class,
LogoutTearDown.class
})
EDIT: Here is an example of both the LoginSetup and LogoutTearDown tests. This solution really should only be for end-to-end tests and comprise a small portion of your testing efforts. fejd provides a solution for a full testing stack which also needs to be considered.
#LargeTest
public class SmokeSetup extends LogInTestFixture {
#Rule
public ActivityTestRule<LoginActivity> mLoginActivity = new ActivityTestRule<>(LoginActivity.class);
#Test
public void testSetup() throws IOException {
onView(withId(R.id.username_field)).perform(replaceText("username"));
onView(withId(R.id.password_field)).perform(replaceText("password"));
onView(withId(R.id.login_button)).perform(click());
}
}
#LargeTest
public class LogoutTearDown extends LogInTestFixture {
#Rule
public ActivityTestRule<MainActivity> mMainActivity = new ActivityTestRule<>(MainActivity.class);
#Test
public void testLogout() throws IOException {
onView(withId(R.id.toolbar_menu)).perform(click());
onView(withId(R.id.logout_button)).perform(click());
}
}
The approach with logging in with #Before is nice but if your login is slow, your combined test time will be very slow.
Here's a great hack that works. The strategy is simple: run every test in order and fail every test before they get a chance to run if a certain test fails (in this case login test).
#RunWith(AndroidJUnit4.class)
#FixMethodOrder(MethodSorters.NAME_ASCENDING)
#LargeTest
public class YourTestsThatDependsOnLogin {
private static failEverything;
#Before
public void beforeTest() {
// Fail every test before it has a chance to run if login failed
if (failEverything) {
Assert.fail("Login failed so every test should fail");
}
}
#Test
public void test0_REQUIREDTEST_login() {
failEverything = true;
// Your code for login
// Your login method must fail the test if it fails.
login();
failEverything = false; // We are safe to continue.
}
// test1 test2 test3 etc...
}
Pros:
What you asked for works and it is fast (if your login is slow)
You can have multiple tests that depend on different logins, meaning you can do a bunch of tests for user1, then a bunch of tests for user2 etc.
Quick to set up.
Cons:
Not standard procedure and someone might wonder why so many tests
fail...
You should mock users instead of actually logging in. You should test your login separately and tests should not depend on each other.
Add the following function in your test file, replace the code in the try
block with yours that performs the login actions.
#Before
fun setUp() {
// Login if it is on the LoginActivity
try {
// Type email and password
Espresso.onView(ViewMatchers.withId(R.id.et_email))
.perform(ViewActions.typeText("a_test_account_username"), ViewActions.closeSoftKeyboard())
Espresso.onView(ViewMatchers.withId(R.id.et_password))
.perform(ViewActions.typeText("a_test_account_password"), ViewActions.closeSoftKeyboard())
// Click login button
Espresso.onView(ViewMatchers.withId(R.id.btn_login)).perform(ViewActions.click())
} catch (e: NoMatchingViewException) {
//view not displayed logic
}
}
With this #Before annotation, this setUp function will be executed before any other tests you have in this test file. If the app lands on Login Activity, do the login in this setUp function. The example here assumes there is an EditText for email and password, as well as a login button. It uses Expresso to type the email and password, then hit the login button. The try catch block is to ensure if you are not landing on the Login Activity, it will catch the error and do nothing, and if you didn't land on the Login Activity, then you are good to go on other tests anyway.
Note: this is Kotlin code, but it looks very similar to Java.
My Application also requires the user to be logged in through-out the test run.
However, I am able to login the first time and the application remembers my username/password throughout the test run. In fact, it remembers the credentials until I force it to forget them or uninstall and install the app again.
During a test run, after every test, my app goes to the background and is resumed again at the beginning of the next test. I am guessing your application requires a user to enter their credentials every time you bring it to the front from the background (banking application maybe?). Is there a setting in your application that will "Remember your credentials"? If yes, you can easily enable it right after you login for the first time in your test run.
Other than that, I think you should talk to the developers about providing you a way to remember your credentials.
If you are using #BeforeClass in Kotlin, you need to place it inside companion object. That way the block under #BeforeClass will be executed just once before the 1st test runs in the class. Also have #AfterClass inside companion object, so that it runs at the very end of the last test of the class.
Keep in mind that once the compiler moves from inside companion object to outside of it, the context of the app under test is lost. You can get back the context by launching the main activity(activity after login) of your app.
companion object {
#ClassRule
#JvmField
val activity = ActivityTestRule(Activity::class.java)
#BeforeClass
#JvmStatic
fun setUp() {
// login block
}
#AfterClass
#JvmStatic
fun tearDown() {
// logout block
}
}
#Test
fun sampleTest() {
activity.launchActivity(Intent())
}
So i have a an android application project and unit test project to it. I am a beginner in Eclipse and maybe i don't understand properly how to work with JUnit.
I have written two simple test methods:
#Test
public void searchTest() {
fail("test is failed");
}
#Test
public void getTest() {
assertTrue(10 > 1);
}
So my problems are
Firstly: It is obvoius that searchTest must fail but when i run it doesn't happend :
Secondly: in some reason i can not debug my test methods. I click right button on serachTest in JUnit dialogue and select "Debug" but debugging is not starting.
Thirdly: Only one test method is showing in in JUnit dialogue however i have two methods with #Test annotation in my class. Why does it happend?
Could anyone help me? Thanks in advance
Think about what you had selected when you said to Run it as a Test. You need to select the class itself rather than a method within it. The JUnit View will only show you the tests that were run.
And you need to set a Breakpoint in your test code to have any observable difference from just Running the test.
I have written two test cases in a package com.app.myapp.test
When I try to run them both of them are not getting executed, only one test case gets executed and stops.
I have written the following testsuite in the same package
AllTests.java
public class AllTests extends TestSuite {
public static Test suite() {
return new TestSuiteBuilder(AllTests.class).includePackages("./src/com.ni.mypaint.test","./src/com.ni.mpaint.test").build();
/* .includeAllPackagesUnderHere()
.build();*/
}
Is the code and location for this testsuite is correct?
Well, certainly leave off the '/src/' portion of the package listing for that invocation. Either way, the easiest and most flexible way to run your tests this is to make sure all your tests are in a subpackage of where AllTests is (e.g. com.app.myapp.test.tests) and use this for the suite:
public static Test suite() {
return new TestSuiteBuilder(AllTests.class)
.includeAllPackagesUnderHere().build();
}
Make sure your tests run individually, too, without the suite runner -- the suite won't pick up your tests if they're set up wrong to begin with.
(This is better than explicitly listing the package name since it's more portable -- you can rename your test package without breaking it, for example.)