diff --git a/.travis.yml b/.travis.yml index 21c1e63..c9ae919 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,8 @@ language: android + +addons: + artifacts: true + android: components: # Uncomment the lines below if you want to @@ -28,7 +32,7 @@ android: #- adb shell input keyevent 82 & # Don't do connectedCheck to skip instrumentTest on Travis -script: "./gradlew build" +script: "./gradlew build --info" jdk: - oraclejdk8 diff --git a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/BaseTestCase.java b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/BaseTestCase.java index 92ff77a..464d839 100644 --- a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/BaseTestCase.java +++ b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/BaseTestCase.java @@ -228,6 +228,8 @@ protected void prepareDependencies(MvcComponent testComponent) throws Exception @After public void tearDown() throws Exception { + navigationManager.navigate(this).back(null); + navigationManager.navigate(this).back(); try { Mvc.graph().getRootComponent().getCache().clear(); Mvc.graph().getRootComponent().detach(component); diff --git a/samples/simple-mvp/app/build.gradle b/samples/simple-mvp/app/build.gradle index 7578e7e..decc972 100644 --- a/samples/simple-mvp/app/build.gradle +++ b/samples/simple-mvp/app/build.gradle @@ -25,6 +25,13 @@ dependencies { compile 'com.jakewharton:butterknife:8.2.1' apt 'com.jakewharton:butterknife-compiler:8.2.1' + + // Testing-only dependencies + androidTestCompile 'com.android.support.test:runner:0.5' + androidTestCompile 'com.android.support.test:rules:0.5' + androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2' + androidTestCompile "com.android.support:support-annotations:$rootProject.supportLibVersion" + androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2' } android { @@ -48,6 +55,8 @@ android { versionName "1.0.0" multiDexEnabled false + + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } sourceSets { diff --git a/samples/simple-mvp/app/src/androidTest/java/com/shipdream/lib/android/mvc/samples/simple/mvp/view/TestDetailScreen.java b/samples/simple-mvp/app/src/androidTest/java/com/shipdream/lib/android/mvc/samples/simple/mvp/view/TestDetailScreen.java new file mode 100644 index 0000000..6981cce --- /dev/null +++ b/samples/simple-mvp/app/src/androidTest/java/com/shipdream/lib/android/mvc/samples/simple/mvp/view/TestDetailScreen.java @@ -0,0 +1,140 @@ +/* + * Copyright 2016 Kejun Xia + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.shipdream.lib.android.mvc.samples.simple.mvp.view; + +import android.content.Intent; +import android.support.test.rule.ActivityTestRule; +import android.support.test.runner.AndroidJUnit4; +import android.test.suitebuilder.annotation.LargeTest; + +import com.shipdream.lib.android.mvc.Mvc; +import com.shipdream.lib.android.mvc.MvcComponent; +import com.shipdream.lib.android.mvc.NavigationManager; +import com.shipdream.lib.android.mvc.samples.simple.mvp.MainActivity; +import com.shipdream.lib.android.mvc.samples.simple.mvp.R; +import com.shipdream.lib.android.mvc.samples.simple.mvp.controller.CounterDetailController; +import com.shipdream.lib.android.mvc.samples.simple.mvp.dto.IpPayload; +import com.shipdream.lib.android.mvc.samples.simple.mvp.factory.ServiceFactory; +import com.shipdream.lib.android.mvc.samples.simple.mvp.http.IpService; +import com.shipdream.lib.poke.Provides; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.IOException; + +import javax.inject.Inject; + +import retrofit2.Call; + +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.action.ViewActions.click; +import static android.support.test.espresso.assertion.ViewAssertions.matches; +import static android.support.test.espresso.matcher.ViewMatchers.withId; +import static android.support.test.espresso.matcher.ViewMatchers.withText; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@RunWith(AndroidJUnit4.class) +@LargeTest +public class TestDetailScreen { + private MvcComponent testComponent; + private Call ipServiceCallMock; + @Inject + private NavigationManager navigationManager; + + @Rule + public ActivityTestRule rule = new ActivityTestRule<>( + MainActivity.class, + false, // initialTouchMode + false); // launchActivity. False to set intent per test); + + @Before + public void prepareInjection() throws Exception { + testComponent = new MvcComponent("TestComponent"); + testComponent.register(new Object(){ + /** + * Prepare objects to mock http calls + * @return + * @throws IOException + */ + @Provides + public ServiceFactory serviceFactory() throws IOException { + ipServiceCallMock = mock(Call.class); + + IpService ipServiceMock = mock(IpService.class); + when(ipServiceMock.getIp(anyString())).thenReturn(ipServiceCallMock); + + ServiceFactory serviceFactoryMock = mock(ServiceFactory.class); + when(serviceFactoryMock.createService(IpService.class)).thenReturn(ipServiceMock); + return serviceFactoryMock; + } + }); + + boolean overriding = true; + Mvc.graph().getRootComponent().attach(testComponent, overriding); + + Mvc.graph().inject(this); + + rule.launchActivity(new Intent()); + + navigationManager.navigate(this).to(CounterDetailController.class); + } + + @After + public void tearDown() throws Exception { + //Important!!! + //To clear fragment manager's back stack + navigationManager.navigate(this).back(null); + navigationManager.navigate(this).back(); + + //Remove overriding component + Mvc.graph().getRootComponent().detach(testComponent); + } + + @Test + public void should_be_able_to_increment_and_decrement_count() { + //Initial value is 0 + onView(withId(R.id.screen_detail_counterDisplay)).check(matches(withText("0"))); + + //Click increment and check text view value + onView(withId(R.id.screen_detail_buttonIncrement)).perform(click()); + onView(withId(R.id.screen_detail_counterDisplay)).check(matches(withText("1"))); + + //Click decrement and check text view value + onView(withId(R.id.screen_detail_buttonDecrement)).perform(click()); + onView(withId(R.id.screen_detail_counterDisplay)).check(matches(withText("0"))); + } + + @Test + public void should_be_able_to_increment_and_decrement_count2() { + //Initial value is 0 + onView(withId(R.id.screen_detail_counterDisplay)).check(matches(withText("0"))); + + //Click increment and check text view value + onView(withId(R.id.screen_detail_buttonIncrement)).perform(click()); + onView(withId(R.id.screen_detail_counterDisplay)).check(matches(withText("1"))); + + //Click decrement and check text view value + onView(withId(R.id.screen_detail_buttonDecrement)).perform(click()); + onView(withId(R.id.screen_detail_counterDisplay)).check(matches(withText("0"))); + } +} diff --git a/samples/simple-mvp/app/src/androidTest/java/com/shipdream/lib/android/mvc/samples/simple/mvp/view/TestMasterScreen.java b/samples/simple-mvp/app/src/androidTest/java/com/shipdream/lib/android/mvc/samples/simple/mvp/view/TestMasterScreen.java new file mode 100644 index 0000000..ab5731b --- /dev/null +++ b/samples/simple-mvp/app/src/androidTest/java/com/shipdream/lib/android/mvc/samples/simple/mvp/view/TestMasterScreen.java @@ -0,0 +1,190 @@ +/* + * Copyright 2016 Kejun Xia + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.shipdream.lib.android.mvc.samples.simple.mvp.view; + +import android.content.Intent; +import android.support.test.espresso.matcher.ViewMatchers; +import android.support.test.rule.ActivityTestRule; +import android.support.test.runner.AndroidJUnit4; +import android.test.suitebuilder.annotation.LargeTest; + +import com.shipdream.lib.android.mvc.Mvc; +import com.shipdream.lib.android.mvc.MvcComponent; +import com.shipdream.lib.android.mvc.NavigationManager; +import com.shipdream.lib.android.mvc.samples.simple.mvp.MainActivity; +import com.shipdream.lib.android.mvc.samples.simple.mvp.R; +import com.shipdream.lib.android.mvc.samples.simple.mvp.dto.IpPayload; +import com.shipdream.lib.android.mvc.samples.simple.mvp.factory.ServiceFactory; +import com.shipdream.lib.android.mvc.samples.simple.mvp.http.IpService; +import com.shipdream.lib.poke.Provides; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.IOException; + +import javax.inject.Inject; + +import retrofit2.Call; +import retrofit2.Response; + +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.action.ViewActions.click; +import static android.support.test.espresso.assertion.ViewAssertions.matches; +import static android.support.test.espresso.matcher.RootMatchers.withDecorView; +import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; +import static android.support.test.espresso.matcher.ViewMatchers.withEffectiveVisibility; +import static android.support.test.espresso.matcher.ViewMatchers.withId; +import static android.support.test.espresso.matcher.ViewMatchers.withText; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@RunWith(AndroidJUnit4.class) +@LargeTest +public class TestMasterScreen { + private MvcComponent testComponent; + private Call ipServiceCallMock; + @Inject + private NavigationManager navigationManager; + + @Rule + public ActivityTestRule rule = new ActivityTestRule<>( + MainActivity.class, + false, // initialTouchMode + false); // launchActivity. False to set intent per test); + + @Before + public void prepareInjection() throws Exception { + testComponent = new MvcComponent("TestComponent"); + testComponent.register(new Object(){ + /** + * Prepare objects to mock http calls + * @return + * @throws IOException + */ + @Provides + public ServiceFactory serviceFactory() throws IOException { + ipServiceCallMock = mock(Call.class); + + IpService ipServiceMock = mock(IpService.class); + when(ipServiceMock.getIp(anyString())).thenReturn(ipServiceCallMock); + + ServiceFactory serviceFactoryMock = mock(ServiceFactory.class); + when(serviceFactoryMock.createService(IpService.class)).thenReturn(ipServiceMock); + return serviceFactoryMock; + } + }); + + boolean overriding = true; + Mvc.graph().getRootComponent().attach(testComponent, overriding); + Mvc.graph().inject(this); + + rule.launchActivity(new Intent()); + } + + @After + public void tearDown() throws Exception { + //Important!!! + //To clear fragment manager's back stack + navigationManager.navigate(this).back(null); + navigationManager.navigate(this).back(); + + //Remove overriding component + Mvc.graph().getRootComponent().detach(testComponent); + } + + @Test + public void should_be_able_to_increment_and_decrement_count() { + //Initial value is 0 + onView(withId(R.id.screen_master_counterDisplay)).check(matches(withText("0"))); + + //Click increment and check text view value + onView(withId(R.id.screen_master_buttonIncrement)).perform(click()); + onView(withId(R.id.screen_master_counterDisplay)).check(matches(withText("1"))); + + //Click decrement and check text view value + onView(withId(R.id.screen_master_buttonDecrement)).perform(click()); + onView(withId(R.id.screen_master_counterDisplay)).check(matches(withText("0"))); + } + + @Test + public void should_carry_count_to_detail_screen() { + //Initial value is 0 + onView(withId(R.id.screen_master_counterDisplay)).check(matches(withText("0"))); + + //Click increment and check text view value + onView(withId(R.id.screen_master_buttonIncrement)).perform(click()); + onView(withId(R.id.screen_master_counterDisplay)).check(matches(withText("1"))); + + //Navigate to detail screen + onView(withId(R.id.fragment_master_buttonShowDetailScreen)).perform(click()); + + //Check count on detail screen + onView(withId(R.id.screen_detail_counterDisplay)).check(matches(withText("1"))); + + //Click decrement and check text view value + onView(withId(R.id.screen_detail_buttonIncrement)).perform(click()); + onView(withId(R.id.screen_detail_counterDisplay)).check(matches(withText("2"))); + + navigationManager.navigate(this).back(); + onView(withId(R.id.screen_master_counterDisplay)).check(matches(withText("2"))); + } + + @Test + public void should_be_able_to_get_ip() throws Exception{ + //Pre check + onView(withId(R.id.fragment_master_ipValue)).check( + matches(withEffectiveVisibility(ViewMatchers.Visibility.INVISIBLE))); + + final String fakeIpResult = "abc.123.456.xyz"; + IpPayload payload = mock(IpPayload.class); + when(payload.getIp()).thenReturn(fakeIpResult); + when(ipServiceCallMock.execute()).thenReturn(Response.success(payload)); + + //Action + onView(withId(R.id.fragment_master_ipRefresh)).perform(click()); + + //Check value + onView(withId(R.id.fragment_master_ipValue)).check(matches(withText(fakeIpResult))); + } + + @Test + public void should_be_able_to_show_network_error_when_getting_ip_failed() throws Exception{ + //Pre check + onView(withId(R.id.fragment_master_ipValue)).check( + matches(withEffectiveVisibility(ViewMatchers.Visibility.INVISIBLE))); + + //Prepare + //Throw an IOException to simulate an network error + IOException ioExceptionMock = mock(IOException.class); + when(ipServiceCallMock.execute()).thenThrow(ioExceptionMock); + + //Action + onView(withId(R.id.fragment_master_ipRefresh)).perform(click()); + + //Check value + onView(withText(R.string.network_error_to_get_ip)).inRoot( + withDecorView(not(is(rule.getActivity().getWindow().getDecorView())))) + .check(matches(isDisplayed())); + } +} diff --git a/samples/simple-mvp/app/src/main/java/com/shipdream/lib/android/mvc/samples/simple/mvp/view/CounterDetailScreen.java b/samples/simple-mvp/app/src/main/java/com/shipdream/lib/android/mvc/samples/simple/mvp/view/CounterDetailScreen.java index 3e42b69..7a36ed3 100644 --- a/samples/simple-mvp/app/src/main/java/com/shipdream/lib/android/mvc/samples/simple/mvp/view/CounterDetailScreen.java +++ b/samples/simple-mvp/app/src/main/java/com/shipdream/lib/android/mvc/samples/simple/mvp/view/CounterDetailScreen.java @@ -30,7 +30,7 @@ import butterknife.OnTouch; public class CounterDetailScreen extends AbstractFragment { - @BindView(R.id.fragment_b_counterDisplay) + @BindView(R.id.screen_detail_counterDisplay) TextView display; @Override @@ -40,7 +40,7 @@ protected Class getControllerClass() { @Override protected int getLayoutResId() { - return R.layout.fragment_counter_detail; + return R.layout.screen_detail; } @Override @@ -53,13 +53,13 @@ public void update() { display.setText(controller.getCount()); } - @OnClick(R.id.fragment_b_buttonAutoIncrement) + @OnClick(R.id.screen_detail_buttonAutoIncrement) void onClick(View v) { Intent intent = new Intent(getActivity(), CountService.class); getActivity().startService(intent); } - @OnTouch(R.id.fragment_b_buttonIncrement) + @OnTouch(R.id.screen_detail_buttonIncrement) boolean onTouchIncrement(final View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: @@ -73,7 +73,7 @@ boolean onTouchIncrement(final View v, MotionEvent event) { return false; } - @OnTouch(R.id.fragment_b_buttonDecrement) + @OnTouch(R.id.screen_detail_buttonDecrement) boolean onTouch(final View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: diff --git a/samples/simple-mvp/app/src/main/java/com/shipdream/lib/android/mvc/samples/simple/mvp/view/CounterMasterInsideView.java b/samples/simple-mvp/app/src/main/java/com/shipdream/lib/android/mvc/samples/simple/mvp/view/CounterMasterInsideView.java index c9c240a..fac3fa1 100644 --- a/samples/simple-mvp/app/src/main/java/com/shipdream/lib/android/mvc/samples/simple/mvp/view/CounterMasterInsideView.java +++ b/samples/simple-mvp/app/src/main/java/com/shipdream/lib/android/mvc/samples/simple/mvp/view/CounterMasterInsideView.java @@ -24,7 +24,7 @@ import butterknife.BindView; public class CounterMasterInsideView extends AbstractFragment { - @BindView(R.id.fragment_a_sub_countInEnglish) + @BindView(R.id.screen_master_sub_countInEnglish) TextView txtCountInEnglish; @Override @@ -34,7 +34,7 @@ protected Class getControllerClass() { @Override protected int getLayoutResId() { - return R.layout.fragment_a_sub; + return R.layout.screen_master_subview; } @Override diff --git a/samples/simple-mvp/app/src/main/java/com/shipdream/lib/android/mvc/samples/simple/mvp/view/CounterMasterScreen.java b/samples/simple-mvp/app/src/main/java/com/shipdream/lib/android/mvc/samples/simple/mvp/view/CounterMasterScreen.java index 8e9b484..d574d82 100644 --- a/samples/simple-mvp/app/src/main/java/com/shipdream/lib/android/mvc/samples/simple/mvp/view/CounterMasterScreen.java +++ b/samples/simple-mvp/app/src/main/java/com/shipdream/lib/android/mvc/samples/simple/mvp/view/CounterMasterScreen.java @@ -40,11 +40,11 @@ public class CounterMasterScreen extends AbstractFragment getControllerClass() { */ @Override protected int getLayoutResId() { - return R.layout.fragment_counter_basic; + return R.layout.screen_master; } - @OnClick(R.id.fragment_a_buttonIncrement) + @OnClick(R.id.screen_master_buttonIncrement) void increment(View v) { controller.increment(v); } - @OnClick(R.id.fragment_a_buttonDecrement) + @OnClick(R.id.screen_master_buttonDecrement) void decrement(View v) { controller.decrement(v); } - @OnClick(R.id.fragment_a_buttonShowDetailScreen) + @OnClick(R.id.fragment_master_buttonShowDetailScreen) void goToDetailPage(View v) { controller.goToDetailScreen(v); } - @OnClick(R.id.fragment_a_ipRefresh) + @OnClick(R.id.fragment_master_ipRefresh) void refreshIp(){ controller.refreshIp(); } @@ -97,7 +97,7 @@ public void onViewReady(View view, Bundle savedInstanceState, Reason reason) { CounterMasterInsideView f = new CounterMasterInsideView(); getChildFragmentManager().beginTransaction() - .replace(R.id.fragment_a_anotherFragmentContainer, f).commit(); + .replace(R.id.screen_master_anotherFragmentContainer, f).commit(); } } @@ -131,7 +131,7 @@ public void showHttpError(int statusCode, String message) { @Override public void showNetworkError(IOException e) { - Toast.makeText(getContext(), String.format("Network error: %s", e.getMessage()) + Toast.makeText(getContext(), R.string.network_error_to_get_ip , Toast.LENGTH_SHORT).show(); } diff --git a/samples/simple-mvp/app/src/main/res/layout/fragment_interim.xml b/samples/simple-mvp/app/src/main/res/layout/fragment_interim.xml deleted file mode 100644 index 3a46f14..0000000 --- a/samples/simple-mvp/app/src/main/res/layout/fragment_interim.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - -