I have downloaded this code from developer.android
public class SpinnerTestActivity extends
ActivityInstrumentationTestCase2<SpinnerActivity> {
private SpinnerActivity mActivity;
private Spinner mSpinner;
private SpinnerAdapter mPlanetData;
public static final int ADAPTER_COUNT = 9;
public static final int INITIAL_POSITION = 0;
public static final int TEST_POSITION = 5;
private String mSelection;
private int mPos;
public SpinnerTestActivity() {
super("com.android.example.spinner", SpinnerActivity.class);
} // end of SpinnerActivityTest constructor definition
#Override
protected void setUp() throws Exception {
super.setUp();
setActivityInitialTouchMode(false);
mActivity = getActivity();
mSpinner = (Spinner) mActivity
.findViewById(com.android.example.spinner.R.id.Spinner01);
mPlanetData = mSpinner.getAdapter();
} // end of setUp() method definition
public void testPreConditions() {
assertTrue(mSpinner.getOnItemSelectedListener() != null);
assertTrue(mPlanetData != null);
assertEquals(mPlanetData.getCount(), ADAPTER_COUNT);
} // end of testPreConditions() method definition
public void testSpinnerUI() {
mActivity.runOnUiThread(new Runnable() {
public void run() {
mSpinner.requestFocus();
mSpinner.setSelection(INITIAL_POSITION);
} // end of run() method definition
} // end of anonymous Runnable object instantiation
); // end of invocation of runOnUiThread
this.sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
for (int i = 1; i <= TEST_POSITION; i++) {
this.sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
} // end of for loop
this.sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
mPos = mSpinner.getSelectedItemPosition();
mSelection = (String) mSpinner.getItemAtPosition(mPos);
TextView resultView = (TextView) mActivity
.findViewById(com.android.example.spinner.R.id.SpinnerResult);
String resultText = (String) resultView.getText();
assertEquals(resultText, mSelection);
}
}
My question is: How the testSpinnerUI is invoked? From where? I have read the junit documentation but cannot figure out.. Thanks!
Stupid question, though. I found the answer in this blog.
The lifecycle of a test case is basically this: construction, setUp(), tests run, tearDown(), and destruction. The setUp() method is used to do any general initialization used by all of specific tests. Each test to be run in the test case is implemented as its own method, where the method name begins with “test”. The tearDown() method is then used to uninitialize any resources acquired by the setUp() method.
Each specific test will have it’s own method beginning with “test” – the “test” method name prefix is case sensitive!
My initial question was how the test method ran, since no one is calling it. But from the above test each method should sart with test, so as to be identified.
Related
I have one Fragment. For the OnClickListener() of all the views that are in the fragment I made another class UtilClickListener. There I am making db call on spinner onItemSelected using room persistence database. The database call first inserts data to the table and then updates an application variable in my application.
So I am trying to access the updated application variable on the spinner onItemSelected() just after the database call. But the variable is not updating at once, later when I click on other views of the fragment then I get the updated value.
Fragment code:
public class Calculator extends Fragment {
#Nullable
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Spinner ageSpinner = rootView.findViewById(R.id.spinner_how_old);
ageSpinner.setOnItemSelectedListener(new UtilOnClickListener(this));
CRSCalculatorAdapter ageListAdapter = new CRSCalculatorAdapter(rootView.getContext(),
android.R.layout.simple_spinner_item,Arrays.asList(rootView.getContext().getResources().getStringArray(R.array.age_group)) );
ageSpinner.setAdapter(ageListAdapter);
}
}
UtilOnClickListener class code:
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
switch (parentSpinnerId[1]) {
case "spinner_how_old}":
mGlobals.setAge(parent.getSelectedItem().toString());
CRSDatabaseInitializer.populateAsync(CRSDatabase.getDatabase(view.getContext()), crsCalculator.getContext(), Constants.QUERY_TYPE_ALL);
mListener.onChangeActionBarTitle(Integer.valueOf(mGlobals.getFinalScore())); // Here I am updating score in the action bar which is updating late on the next view click
break;
}
"CRSDatabaseInitializer" is calling an Asynctask for the database call.
Here is the database initializer code:
public class CRSDatabaseInitializer {
public static void populateAsync(#NonNull final CRSDatabase db, Context context, String typeOfQuery) {
PopulateDbAsync task = new PopulateDbAsync(db, typeOfQuery);
}
private static class PopulateDbAsync extends AsyncTask<Void, Void, Void> {
private final CRSDatabase mDb;
private final String mQueryType;
PopulateDbAsync(CRSDatabase db, String queryType) {
mDb = db;
mQueryType = queryType;
}
#Override
protected Void doInBackground(final Void... params) {
int scoreOfAge = Integer.valueOf(getScoreOnAge(mDb));
mGlobals.setFinalScore(scoreOfAge); // this is the application variable I need to update.
return null;
}
public static int getScoreOnAge(CRSDatabase db) {
int userScore = 0;
if (mGlobals.getAge() != null) {
userScore = Integer.valueOf(db.ageScoreDao().getScore(mGlobals.getAge(), mGlobals.getMarriedOrNot()));
}
return userScore;
}
}
Adding more codes from CRSDatabaseInitializer where I am inserting my data into the room database:
private static void insertNocCode(CRSDatabase db) {
NocCode nocData = new NocCode();
List<String[]> str = insertData(db, "nocCodes.csv");
for (int i = 0; i < str.size(); i++) {
nocData.setmNocCode(str.get(i)[0]);
nocData.setmTitle(str.get(i)[1]);
nocData.setmSkilltype(str.get(i)[2]);
addCRSData(db, nocData);
}
}
insertData(Database db, String filename); is the method where I am reading a csv file and inserting all the columns in the csv file.
public static List<String[]> insertData(CRSDatabase db, String fileName) {
String[] str = new String[5];
ArrayList<String[]> stringArray = new ArrayList<>();
try {
InputStreamReader is = new InputStreamReader(mContext.getAssets()
.open(fileName));
BufferedReader reader = new BufferedReader(is);
String line = "";
while ((line = reader.readLine()) != null) {
str = line.split(",");
stringArray.add(str);
}
} catch (IOException ex) {
ex.printStackTrace();
} finally
{
}
return stringArray;
}
And the insert method definition:
private static NocCode addCRSData(final CRSDatabase db, NocCode nocCode) {
db.nocCodeDao().insert(nocCode);
return nocCode;
}
So here is the update of this problem that I was going through. I solved the issue using handler. When I am making the database call, I am letting the DB to update the variable first , then I am running one handler to get the updated value later in the fragment.
Here is the code I updated in the UtilOnClickListener class.
private static class MyHandler extends Handler {}
private final MyHandler mHandler = new MyHandler();
public static class UtilRunner implements Runnable {
private final WeakReference<Calculator> mActivity;
public UtilRunner(Calculator activity) {
mActivity = new WeakReference<Calculator>(activity);
}
#Override
public void run() {
Calculator activity = mActivity.get();
if (activity.getContext() instanceof MainActivity) {
OnActionBarListener mListener = (OnActionBarListener) activity.getContext();
Globals mGlobals = (Globals) activity.getActivity().getApplicationContext();
mListener.onChangeActionBarTitle(mGlobals.getFinalScore());
}
}
}
And I am running the handler from OnClick of the views:
mHandler.postDelayed(mRunnable, 200);
There are various ways to handle this. In your case I am not able to understand why read operation is getting executed before inserted data is committed even though you are inserting and reading from the same thread. You can have a look on this discussion: stackOverFlow, what I learned from this discussion is that it's always better to take control in your hand because database internal implementation might change from time to time. Let's see the soution:
Wrap the read query inside a transaction either by using annotation #Transaction in Dao class or by wrapping the code for insertion in db.beginTransaction and db.endTransaction.devGuide. This ensures that read can't happen while database is being written.
What I find best for this is using Rx-Java See Introdution. You can do the insertion and then get notified when it completes and perform the read operation. Since insert operation will not return anything, wrap it inside Completable.fromAction. Completable observable has operator obs1.andThen(obs2), as clear from the name first obs1 is completed then only obs2 is executed. Note that your db.ageScoreDao().getScore(..) method should return an observable, hence wrap the return value in an Observable;
Completable.fromAction(new Action(){
#Override
public void run() throws Exception {
db.nocCodeDao().insert(nocCode);
}
}).andThen(return db.ageScoreDao().getScore(..)
.subscribeOn(Scheduler.io()) //do database query on io thread
.observeOn(AndroidScheduler.mainThread())
.subscribeWith(new DisposableObserver<Object>(){
#Override
public void onNext(Object o) {
//Here you update the variable
}
#Override
public void onError(Throwable e) {..}
#Override
public void onComplete() {..}
});
I am trying to write unit test cases for below class. Specially, for 'isSumOfTaskWeightIsValid()' method from the below. it has private member involved it. Could you please help writing test cases for that. I find it difficult because of the 'for loop' in that method where it loops over the 'mTasks'. Thanks in advance.
Class TaskCard {
private List<Integer> mTasks = new ArrayList<>();
private boolean mIsGood;
public TaskCard(boolean isGood) { mIsGood = isGood}
public void setUpListofTasks(DataBaseHelper db){
mTasks.addAll(db.getTasks());
}
public boolean isSumOfTaskWeightIsValid(){
int sum = 0;
for(int taskWeight : mTasks)
{ sum += taskWeight;
}
return (sum>0 || mIsGood);
}
}
You can use #Before annotation to fill your mTasks list and then you can call your isSumOfTaskWeightIsValid method. You also need set your mIsGood parameter in your constructor. Here is a sample test class.
private TaskCard taskCard;
#Before
public void initObjects() {
taskCard = new TaskCard(...); //True or False
//Initialize DataBaseHelper here
DataBaseHelper db = new DataBaseHelper();
taskCard.setUpListofTasks(db);
}
#Test
public void testIsSumOfTaskWeightIsValid() {
// Now your list is filled with the value you prove in #Before
assertTrue(taskCard.isSumOfTaskWeightIsValid());
}
I am trying to test RecyclerView with AndroidJunit4, it is my test code:
#Rule
public ActivityTestRule<ProductListActivity> rule = new ActivityTestRule<>(ProductListActivity.class);
............................
..........................
#Test
public void ensureDataIsLoadingOnSuccess() throws Exception {
ProductListActivity activity = rule.getActivity();
...........................
............
activity.runOnUiThread(new Runnable() {
public void run() {
activity.displayProducts(asList(product1, product2), 0);
}
});
assertEquals(2, mAdapter.getItemCount());
assertThat(((ProductAdapter) mAdapter).getItemAtPosition(0),sameInstance(product1));
assertThat(((ProductAdapter) mAdapter).getItemAtPosition(1),sameInstance(product2));
}
Here is my code for displayProducts() in Activity:
#Override
public void displayProducts(List<Product> products, Integer pageNo) {
progressBar.setVisibility(View.GONE);
if (pageNo == 0 && products.size() == 0) {
noProductTextView.setVisibility(View.VISIBLE);
} else {
mProductAdapter.addProduct(products);
noProductTextView.setVisibility(View.GONE);
productListView.setVisibility(View.VISIBLE);
}
}
It is giving error like:
junit.framework.AssertionFailedError: expected:<2> but was:<0>
at junit.framework.Assert.fail(Assert.java:50)
at junit.framework.Assert.failNotEquals(Assert.java:287)
at junit.framework.Assert.assertEquals(Assert.java:67)
at junit.framework.Assert.assertEquals(Assert.java:199)
at junit.framework.Assert.assertEquals(Assert.java:205)
at com.kaushik.myredmart.ui.ProductListActivityTest.ensureDataIsLoadingOnSuccess(ProductListActivityTest.java:94)
Please help what is the problem in my code?
The reason is that your Espresso test did not wait your loading task which is time-consuming.
You need to use a espresso-idling-resource to tell it to wait this task to finish.
Then you need a class to implement IdlingResource and declare it your Activity.
When your Espresso test run, it will know and wait your long-time consuming task to complete and test the result.
Firstly, add its dependency.
compile "com.android.support.test.espresso:espresso-idling-resource:2.2.2"
Secondly, you need two Java files in folder src/main/java/your-package.
SimpleCountingIdlingResource.java
public final class SimpleCountingIdlingResource implements IdlingResource {
private final String mResourceName;
private final AtomicInteger counter = new AtomicInteger(0);
// written from main thread, read from any thread.
private volatile ResourceCallback resourceCallback;
/**
* Creates a SimpleCountingIdlingResource
*
* #param resourceName the resource name this resource should report to Espresso.
*/
public SimpleCountingIdlingResource(String resourceName) {
mResourceName = checkNotNull(resourceName);
}
#Override public String getName() {
return mResourceName;
}
#Override public boolean isIdleNow() {
return counter.get() == 0;
}
#Override public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
this.resourceCallback = resourceCallback;
}
/**
* Increments the count of in-flight transactions to the resource being monitored.
*/
public void increment() {
counter.getAndIncrement();
}
/**
* Decrements the count of in-flight transactions to the resource being monitored.
*
* If this operation results in the counter falling below 0 - an exception is raised.
*
* #throws IllegalStateException if the counter is below 0.
*/
public void decrement() {
int counterVal = counter.decrementAndGet();
if (counterVal == 0) {
// we've gone from non-zero to zero. That means we're idle now! Tell espresso.
if (null != resourceCallback) {
resourceCallback.onTransitionToIdle();
}
}
if (counterVal < 0) {
throw new IllegalArgumentException("Counter has been corrupted!");
}
}
}
EspressoIdlingResource.java
public class EspressoIdlingResource {
private static final String RESOURCE = "GLOBAL";
private static SimpleCountingIdlingResource mCountingIdlingResource =
new SimpleCountingIdlingResource(RESOURCE);
public static void increment() {
mCountingIdlingResource.increment();
}
public static void decrement() {
mCountingIdlingResource.decrement();
}
public static IdlingResource getIdlingResource() {
return mCountingIdlingResource;
}
}
Ok. Let's go to Activity where you have a time-consuming task.
Firstly, put this method at the bottom.
#VisibleForTesting
public IdlingResource getCountingIdlingResource() {
return EspressoIdlingResource.getIdlingResource();
}
Inside your time-consuming task. you should tell your Espresso to wait like this.
EspressoIdlingResource.increment();
yourTask.run(new Callback() {
void onFinish(){
EspressoIdlingResource.decrement();
}
})
Final step is to define these methods in your UI test class.
#Before
public void registerIdlingResource() {
Espresso.registerIdlingResources(mOnBoardActivityTestRule.getActivity().getCountingIdlingResource());
}
/**
* Unregisters your idling resource so it can be garbage collected and does not leak any memory
*/
#After
public void unregisterIdlingResource() {
Espresso.unregisterIdlingResources(mOnBoardActivityTestRule.getActivity().getCountingIdlingResource());
}
Yeah. Finally we done.
There is one problem I can see here, your are inquiring the List size before the Main/UI thread is able to update it. So, you will have to wait in the current thread till the Activity finished updating the list on Main thread.
You can do,
Thread.sleep(500);
in the Test class to wait, to test the list setting behavior in Activity and you will find the assertion to be valid.
Since, the main thread runs infinitely till the application is running, you will have to implement a callback interface provided by the Activity to be informed about when populating the list is finished.
After running a code inspection through android studio, it highlight that the MainHandler should be static. I move the class to static but now it complain that
"Non-Static method remainingSecondsChanged(int) cannot be referenced from a static context"
public class CountDownView extends FrameLayout {
private static void remainingSecondsChanged(int newVal) {
mRemainingSecs = newVal;
if (mListener != null) {
mListener.onRemainingSecondsChanged(mRemainingSecs);
}
if (newVal == 0) {
// Countdown has finished.
setVisibility(View.INVISIBLE);
if (mListener != null) {
mRemainingSecondsView.setText(null);
mRemainingSecondsView.setBackgroundResource(R.drawable.bracket_view_finder);
mListener.onCountDownFinished();
}
} else {
Locale locale = getResources().getConfiguration().locale;
String localizedValue = String.format(locale, "%d", newVal);
mRemainingSecondsView.setText(localizedValue);
// Schedule the next remainingSecondsChanged() call in 1 second
mHandler.sendEmptyMessageDelayed(SET_TIMER_TEXT, 1000);
}
}
public void startCountDown(int sec) {
if (sec < 0) {
return;
}
if (sec == 0) {
cancelCountDown();
}
mRemainingSecondsView.setBackgroundResource(R.drawable.bracket_count_down);
setVisibility(View.VISIBLE);
remainingSecondsChanged(sec);
}
private static class MainHandler extends Handler {
#Override
public void handleMessage(Message message) {
if (message.what == SET_TIMER_TEXT) {
remainingSecondsChanged(mRemainingSecs - 1);
}
}
}
private static final MainHandler mHandler = new MainHandler();
}
Any idea how to fix it ?
First... Why's Studio showing that message?
Background
Each Handler is associated with a Thread, and all Handler objects on the same Thread share a common Looper object, where they post and read their messages. Thing is... when these objects are non-static well... non-static inner classes hold an implicit reference to their outer class. So the Handler will hold a reference to your Activity, and if this Handler has a delayed message, your Activity will be unable to be garbage collected until this message is processed.
You can read more about it here.
Solution
As for your problem. The first thing you already did, which is make your Handler a static inner class. Now, create a WeakReference to your outer class (Could be an Activity or I believe in this case, your CountDownView).
Now try changing your Handler to something like this (Instead of Activity you could reference your CountDownView):
private static class MainHandler extends Handler {
private final WeakReference<YourActivity> mActivity;
public MainHandler(YourActivity activity) {
mActivity = new WeakReference<YourActivity>(activity);
}
#Override
public void handleMessage(Message message) {
YourActivity activity = mActivity.get();
if (activity != null) {
if (message.what == SET_TIMER_TEXT) {
activity.remainingSecondsChanged(mRemainingSecs - 1);
}
}
}
}
And instantiate it like this:
// this is a reference to your Activity, or your CountDownView, wherever your method is.
private final MainHandler mHandler = new MainHandler(this);
This StackOverflow post here Explains why the inner classes should be static and it is the pretty much same reason why the code analyzer complaints about it,Suppose If you want the members of the containing class to be accessible from your inner class you can make it non static
I am not android programmer but maybe instead of creating inner class which extends Handler than you can create private field like this:
private Handler handler = new Handler() {
public void handleMessage(Message msg) {
//call your non static method here
}
}
Change the constructor of the MainHandler to receive a callback interface
public MainHandler(Callback cb){
this.mCallBack = cb;
}
Then at handleMessage call the callback interface to perform the method
public void handleMessage(Message message) {
if (message.what == SET_TIMER_TEXT) {
mCallBack.someMethod();1);
}
}
At fragment declare interface
public interface Callback
{
void someMethod();
}
Make your fragment implement it.
private final MainHandler mHandler = new MainHandler(this);
Then at the implementation call
remainingSecondsChanged(mRemainingSecs - 1);
This is not the best way to do it but its the fastest with your current design.
I'm using mockito to mock AccountManager inside an Activity test.
So, my test code is as follows:
public class PressuresListActivityUnitTest extends
ActivityUnitTestCase<PressuresListActivity> {
// Test data.
private static final String ACCOUNT_TYPE = "com.example.android";
private static final Account ACCOUNT_1 = new Account("account1#gmail.com", ACCOUNT_TYPE);
private static final Account ACCOUNT_2 = new Account("account2#gmail.com", ACCOUNT_TYPE);
private static final Account[] TWO_ACCOUNTS = { ACCOUNT_1, ACCOUNT_2 };
#Mock
private AccountManager mMockAccountManager;
public PressuresListActivityUnitTest() {
super(PressuresListActivity.class);
}
#Override
protected void setUp() throws Exception {
super.setUp();
setupDexmaker();
// Initialize mockito.
MockitoAnnotations.initMocks(this);
}
public void testAccountNotFound() {
Mockito.when(mMockAccountManager.getAccounts())
.thenReturn(TWO_ACCOUNTS);
Intent intent = new Intent(Intent.ACTION_MAIN);
startActivity(intent, null, null);
}
/**
* Workaround for Mockito and JB-MR2 incompatibility to avoid
* java.lang.IllegalArgumentException: dexcache == null
*
* #see <a href="https://code.google.com/p/dexmaker/issues/detail?id=2">
* https://code.google.com/p/dexmaker/issues/detail?id=2</a>
*/
private void setupDexmaker() {
// Explicitly set the Dexmaker cache, so tests that use mockito work
final String dexCache = getInstrumentation().getTargetContext().getCacheDir().getPath();
System.setProperty("dexmaker.dexcache", dexCache);
}
And the onCreate mthod of activity that will be tested:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pressures_list);
AccountManager am = AccountManager.get(this);
Account[] accounts = am.getAccounts();
if (accounts.length > 0) {
Log.i("TAG", "it works!");
}
}
But when I run the test, AccountManager.getAccounts does NOT return the accounts specified in the test.
Any idea?
After some research, I finally solved the problem.
Android provides some classes to be used inside the tests, like MockContext, IsolatedContext.
http://developer.android.com/reference/android/test/mock/MockContext.html
http://developer.android.com/reference/android/test/IsolatedContext.html
To get this done, I created a subclass of ContextWrapper and overrode(??) getSystemService method.
According to the documentation:
"Proxying implementation of Context that simply delegates all of its calls to another Context. Can be subclassed to modify behavior without changing the original Context."
http://developer.android.com/reference/android/content/ContextWrapper.html
This way, I injected the original context, but modified to fit my needs, inside the Activity using a regular AndroidActivityUnitTestCase.
Check this out:
public class FakeContextWrapper extends ContextWrapper {
private static final String ACCOUNT_TYPE = "com.example.android";
private static final Account ACCOUNT_1 = new Account("account1#gmail.com", ACCOUNT_TYPE);
private static final Account ACCOUNT_2 = new Account("account2#gmail.com", ACCOUNT_TYPE);
private static final Account[] TWO_ACCOUNTS = { ACCOUNT_1, ACCOUNT_2 };
#Mock
private AccountManager mMockAccountManager;
public FakeContextWrapper(Context base) {
super(base);
MockitoAnnotations.initMocks(this);
Mockito.when(mMockAccountManager.getAccounts()).thenReturn(TWO_ACCOUNTS);
}
#Override
public Object getSystemService(String name) {
if (Context.ACCOUNT_SERVICE.equals(name)) {
return mMockAccountManager;
} else {
return super.getSystemService(name);
}
}
}
Inside the test:
public void testAccountNotFound() {
Context context = new FakeContextWrapper(getInstrumentation().getTargetContext());
setActivityContext(context);
Intent intent = new Intent(Intent.ACTION_MAIN);
startActivity(intent, null, null);
// TODO assertions.
}
Finally, the Activity under test:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pressures_list);
AccountManager am = AccountManager.get(this);
Account[] accounts = am.getAccounts();
if (accounts.length == 0) {
// TODO call login.
} else {
Log.i("TAG", "it works!");
}
}
That's not how mockito works.
import static org.mockito.Mockito.*;
...
public void testAccountNotFound() {
AccountManager am = mock(AccountManager.class);
when(am.getAccounts()).thenReturn(TWO_ACCOUNTS);
// this is how you unit test something
assertTrue(am.getAccounts().size == 2);
}
public void testMoreRealWorldExample() {
AccountManager am = mock(AccountManager.class);
when(am.getAccounts()).thenReturn(TWO_ACCOUNTS);
/* try and create an account; createNewAccount() will call
getAccounts() to find out how many accounts there already
are in the system, and due to the above injection, it would
think there are already two. Thus we can test to make sure
users cannot create three or more accounts.
*/
boolean accountCreated = am.createNewAccount();
// maximum two accounts are allowed, so this should return false.
assertFalse(accountCreated);
}
You can't directly use mockito to just arbitrarily inject values in objects and then run an Activity. Mockito is for unit testing your objects, ideally with minimal references to Android-specific objects, though some references will be inevitable.
Please read the cookbook more closely as it is pretty thorough.
If you want to mock and entire Activity, you'll need to look in to Robolectric