This is a second article from series of articles about testing Android project. First article about "Unit testing" you can read here. Full source code you can find on Github. The project on GitHub contains different branches(basic version of app, unit tests for basic version of app, basic version + payment activity, mockito and robolectric tests
Challenge of unit testing
A Unit test should test a class in isolation. The achievement of this goal is completed because many Java classes depends on other classes. For solving this problem we use test doubles.
Classification of different objects
A dummy object is passed around but never used, that mean its methods are never used, object like this passed as a parameter.
A fake object has working implementation, but usually simplified.
A stub class is an partial implementation for a class or an interface with the purpose using it for testing.
A mock object is a dummy implementation for a class or an interface which defined for the methods calls.
Mock object generation
You can create a mock object manually or use a mock frameworks to simulate this class. As example, a mock object is a data provider. An application use real database in production, but for testing, the better way is to use a mock object which simulates the database and ensures that the test conditions are always the same.
Mocking frameworks
A mock frameworks allow to create mock object as simple as possible. Popular frameworks are EasyMock, jMock and Mockito.
Short introduction to Mockito framework
Mockito is a popular mock framework which can be used with JUnit. Mockito allows you to create and configure mock objects.
Adding mockito as dependency to the project
If you use gradle as your build system, add the following dependency to your build.gradle
file.
dependencies {
...
testCompile 'org.mockito:mockito-core:1.10.19'
}
If you use maven you can search for "org.mockito" and "mockito-core" via the Maven search website.
Using the Mockito API
Creating and configuring mock objects
Mockito allows use static mock()
method for creating mock objects.
MyClass test = Mockito.mock(MyClass.class);</pre>
// or
@RunWith(MockitoJUnitRunner.class)
public class MockitoTest {
@Mock MyClass test;
...
}
The when(...).thenReturn(...)
call allows to set up specific condition and return values for this term. You can also use methods like anyInt()
, anyFloat()
, anyList()
, etc. to define an independency of input value and the method must return the certain value.
import static org.mockito.Mockito.*;
import static org.junit.Assert.*;
@Test
public void testGetMockedCoffeeId() {
// create mock
Coffee coffee = Mockito.mock(Coffee .class);
// define return value for method getCoffeeId()
when(test.getCoffeeId()).thenReturn(43);
// use mock in test
assertEquals(test.getCoffeeId(), 43);
}
// Demonstrates the return of multiple values
@Test
public void testMultipleValues() {
List<String> list = new ArrayList<>();
list.add("first");
list.add("second");
Iterator<Integer> iterator = Mockito.mock(Iterator.class);
when(iterator.next()).thenReturn(0).thenReturn(1);
assertEquals(list.get(0), list.get(iterator.next()));
assertEquals(list.get(1), list.get(iterator.next()));
}
// this test demonstrates how to return values based on the input
@Test
public void testReturnValueDependentOnMethodParameter() {
final String MOCKITO = "mockito";
final String ESPRESSO = "espresso";
final int MOCKITO_INT = 1;
final int ESPRESSO_INT = 2;
Comparable comparable = mock(Comparable.class);
when(comparable.compareTo(MOCKITO)).thenReturn(MOCKITO_INT);
when(comparable.compareTo(ESPRESSO)).thenReturn(ESPRESSO_INT);
assertEquals(MOCKITO_INT, comparable.compareTo(MOCKITO));
assertEquals(ESPRESSO_INT, comparable.compareTo(ESPRESSO));
}
// this test demonstrates how to return values independent of the input value
@Test
public void testReturnValueInDependentOnMethodParameter() {
Comparable comparable = mock(Comparable.class);
when(comparable.compareTo(anyInt())).thenReturn(-1);
//assert
assertEquals(-1 ,c.compareTo(9));
}
// return a value based on the type of the provide parameter
@Test
public void testReturnValueInDependentOnMethodParameter() {
Comparable c= mock(Comparable.class);
when(c.compareTo(isA(String.class))).thenReturn(0);
//assert
Todo todo = new Todo(5);
assertEquals(todo ,c.compareTo(new String(“test”)));
}
The doReturn(...).when(...).methodCall
works similar but is useful for void
methods.
@Test
public void spyLinkedListTest() {
// Lets mock a LinkedList
List list = new LinkedList();
list.add("first");
list.add("second");
List spy = Mockito.spy(list);
//You have to use doReturn() for stubbing
doReturn("foo").when(spy).get(0);
when(spy.get(0)).thenReturn("foo");
assertEquals("foo", spy.get(0));
}
The doThrow
variant can be used for methods which return void
to through an exception.
import static org.mockito.Mockito.*;
import static org.junit.Assert.*;
@Test(expected=IOException.class)
public void IOExceptionTest() {
// create an configure mock
OutputStream mockStream = mock(OutputStream.class);
doThrow(new IOException()).when(mockStream).close();
// use mock
OutputStreamWriter streamWriter= new OutputStreamWriter(mockStream);
streamWriter.close();
}
Verify the calls on the mock objects
You can also use verify()
method to ensure that the method was called.
@Test
public void verifyTest() {
// create and configure mock
Coffee coffee = mock(Coffee.class);
when(coffee.getId()).thenReturn(43);
// call method testing on the mock with parameter 12
coffee.setId(12);
coffee.getId();
coffee.getId();
// now check if method testing was called with the parameter 12
verify(coffee).setId(Matchers.eq(12));
// was the method called twice?
verify(coffee, times(2)).getId();
// other alternatives for verifiying the number of method calls for a method
verify(coffee, never()).setDescription("never called");
verify(coffee, atLeastOnce()).getId();
verify(coffee, atLeast(2)).getId();
verify(coffee, times(5)).setName("called five times");
verify(coffee, atMost(2)).getId();
}
Limitations
- Final classes
- Anonymous classes
- Primitive types
I want to show you how we can test Android things.
@Test
public void shouldBeCreatedIntent() {
Context context = Mockito.mock(Context.class);
Intent intent = MainActivity.createQuery(context, "query", "value");
assertNotNull(intent);
Bundle extras = intent.getExtras();
assertNotNull(extras);
assertEquals("query", extras.getString("QUERY"));
assertEquals("value", extras.getString("VALUE"));
}
Unfortunately if we run this test, we get next error log.
java.lang.RuntimeException: Method putExtra in android.content.Intent not mocked. See http://g.co/androidstudio/not-mocked for details.
at android.content.Intent.putExtra(Intent.java)
at PaymentActivity.createIntent(PaymentActivity.java:15)
at MainActivityTest.shouldBeCreatedIntent(MainActivityTest.java:118)
Many basic methods from basic classes are not mocked. Fortunately you can use Robolectric.
Robolectric
Unit testing has been particularly difficult in Android, although with Robolectric it is much easier. This library also allows you to mock the Android SDK by removing the stubs that throw RuntimeExceptions
.
Robolectric is a unit testing library that allows you to run your test in a Java Virtual Machine (JVM). This library also allow you to do Test Driven Development (TDD).
Robolectric replaced all Android classes by so-called shadow objects.
If a method is implemented by Robolectric, it forwards these method calls to the shadow object which act similar to the objects of the Android SDK. If a method is not implemented by the shadow object, it simply returns a default value, e.g., null
or 0
.
I want to show to you how to configure Robolectric with Android application. The easiest way is updating build.gradle
file.
apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion "23.0.2"
defaultConfig {
minSdkVersion 14
targetSdkVersion 23
...
useLibrary 'org.apache.http.legacy'
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
...
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
...
testCompile 'org.robolectric:robolectric:3.0'
testCompile 'org.robolectric:shadows-support-v4:3.0'
}
If you want to use Shadow objects you must add this "org.robolectric:shadows-support-v4:3.0" dependency to your project and "useLibrary org.apache.http.legacy". Probably, you know that AndroidHttpClient
was removed from the SDK in v23 of the build tools.
After it we can create first Android test with Robolectric. You need to create a new class for it in tests package name (android test package we use for instrumental test, test package we use for local JVM tests). Also need to setup Runner for our class and config.
@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21)
public class MainActivityTest {
@Test
public void shouldNotBeNull() {
MainActivity activity = Robolectric.setupActivity(MainActivity.class);
assertNotNull(activity);
}
}
We also must add few changes to the application. First of all, I want to add a payment activity for the application. I need to create a layout for it, an activity class and to add information about new activity to AndroidManifest.xml
file.
The activity_payment
layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin">
<TextView
android:id="@+id/payment_data"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="@string/total_price" />
</LinearLayout>
The PaymentActivity
class.
public class PaymentActivity extends AppCompatActivity {
public final static String TOTAL_PRICE = "total_price";
public static Intent createIntent(Context context, float totalPrice) {
Intent intent = new Intent(context, PaymentActivity.class);
intent.putExtra(TOTAL_PRICE, totalPrice);
return intent;
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_payment);
if (getIntent()!= null && getIntent().getExtras() != null) {
float totalPrice = getIntent().getExtras().getFloat(TOTAL_PRICE);
TextView paymentData = (TextView) findViewById(R.id.payment_data);
if (paymentData != null) {
paymentData.setText(getString(R.string.total_price, totalPrice));
}
}
}
}
The last thing is updating AndroidManifest.xml
file.
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
...
<activity android:name=".PaymentActivity"/>
</application>
We must also update MainActivity
and add a "Pay" button and the setOnClickListener
to this button. For starting a new activity I use next line of code
startActivity(PaymentActivity.createIntent(getApplicationContext(), mOrder.getTotalPrice()));
In the first Robolectric test we checked if the MainActivity
exists. First of all, we need to set up an activity. In this case, it’s MainActivity
. Next step is comparing this object with a null
.
@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21)
public class MainActivityTest {
@Test
public void shouldNotBeNull() {
MainActivity activity = Robolectric.setupActivity(MainActivity.class);
assertNotNull(activity);
}
...
}
Next test case for verification of price labels. There are two labels for displaying a price in the application.
@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21)
public class MainActivityTest {
...
@Test
public void shouldHavePriceLabels() {
MainActivity activity = Robolectric.setupActivity(MainActivity.class);
TextView coffeePrice = (TextView) activity.findViewById(R.id.coffee_price);
assertNotNull(coffeePrice);
assertEquals(View.VISIBLE, coffeePrice.getVisibility());
assertEquals(activity.getString(R.string.coffee_price, 5.0f), coffeePrice.getText());
TextView totalPrice = (TextView) activity.findViewById(R.id.total_price);
assertNotNull(totalPrice);
assertEquals(View.VISIBLE, totalPrice.getVisibility());
assertEquals(activity.getString(R.string.total_price, 0.0f), totalPrice.getText());
}
...
}</pre>
<p>Next test case verifies a coffee cup picker (increment button + count label + decrement button).</p>
<pre class="EnlighterJSRAW" data-enlighter-language="java">@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21)
public class MainActivityTest {
...
@Test
public void shouldHaveCoffeeCupPicker() {
MainActivity activity = Robolectric.setupActivity(MainActivity.class);
Button incrementButton = (Button) activity.findViewById(R.id.coffee_increment);
assertNotNull(incrementButton);
assertEquals(View.VISIBLE, incrementButton.getVisibility());
Button decrementButton = (Button) activity.findViewById(R.id.coffee_decrement);
assertNotNull(decrementButton);
assertEquals(View.VISIBLE, decrementButton.getVisibility());
TextView coffeeCount = (TextView) activity.findViewById(R.id.coffee_count);
assertNotNull(coffeeCount);
assertEquals(View.VISIBLE, coffeeCount.getVisibility());
assertEquals(activity.getString(R.string.default_coffee_count), coffeeCount.getText());
}
...
}
Next test case verifies how to increment and decrement buttons work.
@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21)
public class MainActivityTest {
...
@Test
public void shouldChangeCoffeeCupCountOnIncrementAndDecrementButtons() {
MainActivity activity = Robolectric.setupActivity(MainActivity.class);
int count = 0;
TextView coffeeCount = (TextView) activity.findViewById(R.id.coffee_count);
assertNotNull(coffeeCount);
assertEquals(View.VISIBLE, coffeeCount.getVisibility());
assertEquals(String.valueOf(count), coffeeCount.getText());
Button incrementButton = (Button) activity.findViewById(R.id.coffee_increment);
assertNotNull(incrementButton);
assertEquals(View.VISIBLE, incrementButton.getVisibility());
Button decrementButton = (Button) activity.findViewById(R.id.coffee_decrement);
assertNotNull(decrementButton);
assertEquals(View.VISIBLE, decrementButton.getVisibility());
incrementButton.performClick();
assertEquals(String.valueOf(++count), coffeeCount.getText());
decrementButton.performClick();
assertEquals(String.valueOf(--count), coffeeCount.getText());
//Should be previous value because count can be just positive
decrementButton.performClick();
assertEquals(String.valueOf(count), coffeeCount.getText());
}
...
}
Next test case verifies if pay button exists.
@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21)
public class MainActivityTest {
...
@Test
public void shouldHavePayButton() {
MainActivity activity = Robolectric.setupActivity(MainActivity.class);
Button payButton = (Button) activity.findViewById(R.id.pay);
assertNotNull(payButton);
assertEquals(View.VISIBLE, payButton.getVisibility());
assertEquals(activity.getString(R.string.pay), payButton.getText());
}
...
}
Next test case verifies correctness of Intent
for launching PaymentActivity
.
@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21)
public class MainActivityTest {
...
@Test
public void shouldBeCreatedIntent() {
final float TEST_PRICE = 12.0f;
Context context = RuntimeEnvironment.application.getApplicationContext();
Intent intent = PaymentActivity.createIntent(context, TEST_PRICE);
assertNotNull(intent);
Bundle bundle = intent.getExtras();
assertEquals(TEST_PRICE, bundle.getFloat(PaymentActivity.TOTAL_PRICE), 0.00001f);
}
...
}
The last test case for MainActivity
starts PaymentActivity
after click to "Pay" button.
@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21)
public class MainActivityTest {
...
@Test
public void shouldStartActivityOnPayButton() {
MainActivity activity = Robolectric.setupActivity(MainActivity.class);
Button payButton = (Button) activity.findViewById(R.id.pay);
assertNotNull(payButton);
payButton.performClick();
ShadowActivity shadowActivity = shadowOf(activity);
Intent startedIntent = shadowActivity.getNextStartedActivity();
ShadowIntent shadowIntent = shadowOf(startedIntent);
assertEquals(PaymentActivity.class.getName(),
shadowIntent.getComponent().getClassName());
}
}
You can find full source code of this class below.
@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21)
public class MainActivityTest {
@Test
public void shouldNotBeNull() {
MainActivity activity = Robolectric.setupActivity(MainActivity.class);
assertNotNull(activity);
}
@Test
public void shouldHavePriceLabels() {
MainActivity activity = Robolectric.setupActivity(MainActivity.class);
TextView coffeePrice = (TextView) activity.findViewById(R.id.coffee_price);
assertNotNull(coffeePrice);
assertEquals(View.VISIBLE, coffeePrice.getVisibility());
assertEquals(activity.getString(R.string.coffee_price, 5.0f), coffeePrice.getText());
TextView totalPrice = (TextView) activity.findViewById(R.id.total_price);
assertNotNull(totalPrice);
assertEquals(View.VISIBLE, totalPrice.getVisibility());
assertEquals(activity.getString(R.string.total_price, 0.0f), totalPrice.getText());
}
@Test
public void shouldHaveCoffeeCupPicker() {
MainActivity activity = Robolectric.setupActivity(MainActivity.class);
Button incrementButton = (Button) activity.findViewById(R.id.coffee_increment);
assertNotNull(incrementButton);
assertEquals(View.VISIBLE, incrementButton.getVisibility());
Button decrementButton = (Button) activity.findViewById(R.id.coffee_decrement);
assertNotNull(decrementButton);
assertEquals(View.VISIBLE, decrementButton.getVisibility());
TextView coffeeCount = (TextView) activity.findViewById(R.id.coffee_count);
assertNotNull(coffeeCount);
assertEquals(View.VISIBLE, coffeeCount.getVisibility());
assertEquals(activity.getString(R.string.default_coffee_count), coffeeCount.getText());
}
@Test
public void shouldChangeCoffeeCupCountOnIncrementAndDecrementButtons() {
MainActivity activity = Robolectric.setupActivity(MainActivity.class);
int count = 0;
TextView coffeeCount = (TextView) activity.findViewById(R.id.coffee_count);
assertNotNull(coffeeCount);
assertEquals(View.VISIBLE, coffeeCount.getVisibility());
assertEquals(String.valueOf(count), coffeeCount.getText());
Button incrementButton = (Button) activity.findViewById(R.id.coffee_increment);
assertNotNull(incrementButton);
assertEquals(View.VISIBLE, incrementButton.getVisibility());
Button decrementButton = (Button) activity.findViewById(R.id.coffee_decrement);
assertNotNull(decrementButton);
assertEquals(View.VISIBLE, decrementButton.getVisibility());
incrementButton.performClick();
assertEquals(String.valueOf(++count), coffeeCount.getText());
decrementButton.performClick();
assertEquals(String.valueOf(--count), coffeeCount.getText());
//Should be previous value because count can be just positive
decrementButton.performClick();
assertEquals(String.valueOf(count), coffeeCount.getText());
}
@Test
public void shouldHavePayButton() {
MainActivity activity = Robolectric.setupActivity(MainActivity.class);
Button payButton = (Button) activity.findViewById(R.id.pay);
assertNotNull(payButton);
assertEquals(View.VISIBLE, payButton.getVisibility());
assertEquals(activity.getString(R.string.pay), payButton.getText());
}
@Test
public void shouldBeCreatedIntent() {
final float TEST_PRICE = 12.0f;
Context context = RuntimeEnvironment.application.getApplicationContext();
Intent intent = PaymentActivity.createIntent(context, TEST_PRICE);
assertNotNull(intent);
Bundle bundle = intent.getExtras();
assertEquals(TEST_PRICE, bundle.getFloat(PaymentActivity.TOTAL_PRICE), 0.00001f);
}
@Test
public void shouldStartActivityOnPayButton() {
MainActivity activity = Robolectric.setupActivity(MainActivity.class);
Button payButton = (Button) activity.findViewById(R.id.pay);
assertNotNull(payButton);
payButton.performClick();
ShadowActivity shadowActivity = shadowOf(activity);
Intent startedIntent = shadowActivity.getNextStartedActivity();
ShadowIntent shadowIntent = shadowOf(startedIntent);
assertEquals(PaymentActivity.class.getName(),
shadowIntent.getComponent().getClassName());
}
}
Unfortunately, this source code is not really good. I will show how to improve this code. As you can see from previous examples, almost all of the methods have creation and initialization code of activity. Many other tests create and initialize views, like TextView
and Button
s, all these parts of code we can move to method with @Before
annotation because method with @Before
annotation starts before each test. Next code snippet contain all changes in the MainActivityTest
class.
@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21)
public class MainActivityTest {
private MainActivity mActivity;
private TextView mCoffeePrice;
private TextView mTotalPrice;
private Button mIncrementButton;
private Button mDecrementButton;
private TextView mCoffeeCount;
private Button mPayButton;
private Context mContext;
@Before
public void setUp() {
mContext = RuntimeEnvironment.application.getApplicationContext();
mActivity = Robolectric.setupActivity(MainActivity.class);
mCoffeePrice = (TextView) mActivity.findViewById(R.id.coffee_price);
mTotalPrice = (TextView) mActivity.findViewById(R.id.total_price);
mIncrementButton = (Button) mActivity.findViewById(R.id.coffee_increment);
mDecrementButton = (Button) mActivity.findViewById(R.id.coffee_decrement);
mCoffeeCount = (TextView) mActivity.findViewById(R.id.coffee_count);
mPayButton = (Button) mActivity.findViewById(R.id.pay);
}
@Test
public void shouldNotBeNull() {
assertNotNull(mActivity);
}
@Test
public void shouldHavePriceLabels() {
assertNotNull(mCoffeePrice);
assertEquals(View.VISIBLE, mCoffeePrice.getVisibility());
assertEquals(mActivity.getString(R.string.coffee_price, 5.0f), mCoffeePrice.getText());
assertNotNull(mTotalPrice);
assertEquals(View.VISIBLE, mTotalPrice.getVisibility());
assertEquals(mActivity.getString(R.string.total_price, 0.0f), mTotalPrice.getText());
}
@Test
public void shouldHaveCoffeeCupPicker() {
assertNotNull(mIncrementButton);
assertEquals(View.VISIBLE, mIncrementButton.getVisibility());
assertNotNull(mDecrementButton);
assertEquals(View.VISIBLE, mDecrementButton.getVisibility());
assertNotNull(mCoffeeCount);
assertEquals(View.VISIBLE, mCoffeeCount.getVisibility());
assertEquals(mActivity.getString(R.string.default_coffee_count), mCoffeeCount.getText());
}
@Test
public void shouldChangeCoffeeCupCountOnIncrementAndDecrementButtons() {
int count = 0;
assertNotNull(mCoffeeCount);
assertEquals(View.VISIBLE, mCoffeeCount.getVisibility());
assertEquals(String.valueOf(count), mCoffeeCount.getText());
assertNotNull(mIncrementButton);
assertEquals(View.VISIBLE, mIncrementButton.getVisibility());
assertNotNull(mDecrementButton);
assertEquals(View.VISIBLE, mDecrementButton.getVisibility());
mIncrementButton.performClick();
assertEquals(String.valueOf(++count), mCoffeeCount.getText());
mDecrementButton.performClick();
assertEquals(String.valueOf(--count), mCoffeeCount.getText());
//Should be previous value because count can be just positive
mDecrementButton.performClick();
assertEquals(String.valueOf(count), mCoffeeCount.getText());
}
@Test
public void shouldHavePayButton() {
assertNotNull(mPayButton);
assertEquals(View.VISIBLE, mPayButton.getVisibility());
assertEquals(mActivity.getString(R.string.pay), mPayButton.getText());
}
@Test
public void shouldBeCreatedIntent() {
final float TEST_PRICE = 12.0f;
Intent intent = PaymentActivity.createIntent(mContext, TEST_PRICE);
assertNotNull(intent);
Bundle bundle = intent.getExtras();
assertEquals(TEST_PRICE, bundle.getFloat(PaymentActivity.TOTAL_PRICE), 0.00001f);
}
@Test
public void shouldStartActivityOnPayButton() {
assertNotNull(mPayButton);
mPayButton.performClick();
ShadowActivity shadowActivity = shadowOf(mActivity);
Intent startedIntent = shadowActivity.getNextStartedActivity();
ShadowIntent shadowIntent = shadowOf(startedIntent);
assertEquals(PaymentActivity.class.getName(),
shadowIntent.getComponent().getClassName());
}
}
Before writing next tests you must know that we can get created activity in some ways. First of all, you can create an activity with setupActivity(Class<T> activityClass)
method, but it not a the best way if you want to pass additional parameter to the Activity
via Intent
. Another way is using buildActivity(Class<T> activityClass)
method with next sequence of methods: withIntent(SOME_INTENT)
, create()
, start()
, resume
, visible()
, and get()
; Of course, you can add a change or add some methods to the sequence. You must also know that setupActivity
method can do next sequence of actions: create()
, start()
, postCreate(null)
, resume()
, visible()
, and get()
.
MainActivity mActivity = Robolectric.setupActivity(MainActivity.class);
MainActivity mActivity = Robolectric.buildActivity(MainActivity.class)
.withIntent(MainActivity.createIntent(RuntimeEnvironment.application, TOTAL_PRICE))
.create()
.start()
.resume()
.visible()
.get();
We can start to write tests for the PaymentActivity
.
@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21)
public class PaymentActivityTest {
private static final float TOTAL_PRICE = 15.0f;
private PaymentActivity mActivity;
private TextView mTotalPrice;
@Before
public void setUp() {
mActivity = Robolectric.buildActivity(PaymentActivity.class)
.withIntent(PaymentActivity.createIntent(RuntimeEnvironment.application, TOTAL_PRICE))
.create()
.start()
.resume()
.visible()
.get();
mTotalPrice = (TextView) mActivity.findViewById(R.id.payment_data);
}
@Test
public void shouldNotBeNull() {
assertNotNull(mActivity);
}
@Test
public void shouldHaveTotalPrice() {
assertNotNull(mTotalPrice);
assertEquals(View.VISIBLE, mTotalPrice.getVisibility());
assertEquals(mActivity.getString(R.string.total_price, TOTAL_PRICE), mTotalPrice.getText());
}
}
We finished with all tests for this application. Right now, we can run all tests. As you can see all tests works correctly.
Source code separates to different branches on GitHub
Resources:
Thank you for your time.