From a53e967bf4d7d01fae0f3e549075f00fd16ebac8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juraj=20Nov=C3=A1k?= Date: Wed, 18 Oct 2017 09:36:15 +0200 Subject: [PATCH] Add basic instrumented test --- library/build.gradle | 13 +- library/src/androidTest/AndroidManifest.xml | 8 + .../viewmodel/ViewModelActivityTest.java | 157 ++++++++++++++++++ .../fixture/activity/IVMTestActivityView.java | 9 + .../fixture/activity/VMTestActivity.java | 72 ++++++++ .../activity/VMTestActivityViewModel.java | 39 +++++ .../fixture/fragment/IVMTestFragmentView.java | 9 + .../fixture/fragment/VMTestFragment.java | 33 ++++ .../fragment/VMTestFragmentViewModel.java | 42 +++++ .../eu/viewmodelsample/ApplicationTest.java | 13 -- .../inloop/viewmodel/ViewModelProvider.java | 9 + sample/build.gradle | 12 +- 12 files changed, 393 insertions(+), 23 deletions(-) create mode 100644 library/src/androidTest/AndroidManifest.xml create mode 100644 library/src/androidTest/java/eu/inloop/viewmodel/ViewModelActivityTest.java create mode 100644 library/src/androidTest/java/eu/inloop/viewmodel/fixture/activity/IVMTestActivityView.java create mode 100644 library/src/androidTest/java/eu/inloop/viewmodel/fixture/activity/VMTestActivity.java create mode 100644 library/src/androidTest/java/eu/inloop/viewmodel/fixture/activity/VMTestActivityViewModel.java create mode 100644 library/src/androidTest/java/eu/inloop/viewmodel/fixture/fragment/IVMTestFragmentView.java create mode 100644 library/src/androidTest/java/eu/inloop/viewmodel/fixture/fragment/VMTestFragment.java create mode 100644 library/src/androidTest/java/eu/inloop/viewmodel/fixture/fragment/VMTestFragmentViewModel.java delete mode 100644 library/src/androidTest/java/sample/viewmodel/inloop/eu/viewmodelsample/ApplicationTest.java diff --git a/library/build.gradle b/library/build.gradle index d0710b5..ef5bdd7 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -3,7 +3,7 @@ apply plugin: 'maven' android { compileSdkVersion 26 - buildToolsVersion '25.0.3' + buildToolsVersion '26.0.2' defaultConfig { minSdkVersion 15 @@ -11,6 +11,7 @@ android { versionCode 1 versionName VERSION_NAME consumerProguardFiles 'proguard-rules.pro' + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } compileOptions { sourceCompatibility JavaVersion.VERSION_1_7 @@ -20,13 +21,17 @@ android { } dataBinding { - enabled = true; + enabled = true } } dependencies { - compile 'com.android.support:support-fragment:26.0.1' - compile 'com.android.support:appcompat-v7:26.0.1' + compile 'com.android.support:support-fragment:26.1.0' + compile 'com.android.support:appcompat-v7:26.1.0' + + androidTestCompile 'junit:junit:4.12' + androidTestCompile 'com.android.support.test:runner:1.0.1' + androidTestCompile 'com.android.support.test:rules:1.0.1' } task androidJavadocs(type: Javadoc) { diff --git a/library/src/androidTest/AndroidManifest.xml b/library/src/androidTest/AndroidManifest.xml new file mode 100644 index 0000000..2e06fd1 --- /dev/null +++ b/library/src/androidTest/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + + + + diff --git a/library/src/androidTest/java/eu/inloop/viewmodel/ViewModelActivityTest.java b/library/src/androidTest/java/eu/inloop/viewmodel/ViewModelActivityTest.java new file mode 100644 index 0000000..60dc5fb --- /dev/null +++ b/library/src/androidTest/java/eu/inloop/viewmodel/ViewModelActivityTest.java @@ -0,0 +1,157 @@ +package eu.inloop.viewmodel; + + +import android.content.pm.ActivityInfo; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.MediumTest; +import android.support.test.filters.SmallTest; +import android.support.test.rule.ActivityTestRule; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Map; + +import eu.inloop.viewmodel.fixture.activity.VMTestActivity; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; + + +@RunWith(AndroidJUnit4.class) +public final class ViewModelActivityTest { + + @Rule + public final ActivityTestRule mActivityTestRule = + new ActivityTestRule<>(VMTestActivity.class, false, false); + + @SmallTest + @Test + public void viewModelActivity_onBindView_test() { + mActivityTestRule.launchActivity(VMTestActivity.makeIntent(InstrumentationRegistry.getContext(), true)); + + assertThat(mActivityTestRule.getActivityResult().getResultCode(), is(VMTestActivity.RESULT_CODE_OK)); + } + + @SmallTest + @Test + public void viewModelActivity_getViewModel_getView_test() { + mActivityTestRule.launchActivity(VMTestActivity.makeIntent(InstrumentationRegistry.getContext(), false)); + mActivityTestRule.getActivity().getViewModel().loadData(); + + assertThat(mActivityTestRule.getActivityResult().getResultCode(), is(VMTestActivity.RESULT_CODE_OK)); + } + + @SmallTest + @Test + public void viewModelActivity_getViewModel_getViewOptional_test() { + mActivityTestRule.launchActivity(VMTestActivity.makeIntent(InstrumentationRegistry.getContext(), false)); + mActivityTestRule.getActivity().getViewModel().loadDataOptional(); + + assertThat(mActivityTestRule.getActivityResult().getResultCode(), is(VMTestActivity.RESULT_CODE_OK)); + } + + @SmallTest + @Test + public void viewModelActivity_clearView_test() { + mActivityTestRule.launchActivity(VMTestActivity.makeIntent(InstrumentationRegistry.getContext(), false)); + mActivityTestRule.getActivity().getViewModel().clearView(); + + assertThat(mActivityTestRule.getActivity().getViewModel().getView(), is(nullValue())); + } + + @SmallTest + @Test + public void viewModelActivity_uniqueIdentifier_test() { + mActivityTestRule.launchActivity(VMTestActivity.makeIntent(InstrumentationRegistry.getContext(), false)); + String uniqueIdentifier = mActivityTestRule.getActivity().getViewModel().getUniqueIdentifier(); + + assertThat(uniqueIdentifier, is(notNullValue())); + } + + @SmallTest + @Test + public void viewModelActivity_fragment_getView_test() { + mActivityTestRule.launchActivity(VMTestActivity.makeIntent(InstrumentationRegistry.getContext(), false)); + + mActivityTestRule.getActivity().getTestFragment().getViewModel().loadData(); + + assertThat(mActivityTestRule.getActivityResult().getResultCode(), is(VMTestActivity.RESULT_CODE_OK)); + } + + @SmallTest + @Test + public void viewModelActivity_fragment_remove_test() { + mActivityTestRule.launchActivity(VMTestActivity.makeIntent(InstrumentationRegistry.getContext(), false)); + + String uniqueIdentifierActivity = mActivityTestRule.getActivity().getViewModel().getUniqueIdentifier(); + String uniqueIdentifierFragment = mActivityTestRule.getActivity().getTestFragment().getViewModel().getUniqueIdentifier(); + + Map> viewModels = + mActivityTestRule.getActivity().getViewModelProvider().getViewModels(); + + assertThat(viewModels.containsKey(uniqueIdentifierActivity), is(true)); + assertThat(viewModels.containsKey(uniqueIdentifierFragment), is(true)); + + InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { + @Override + public void run() { + mActivityTestRule.getActivity().removeTestFragment(); + } + }); + + //Check If ViewModel is removed after removing fragment + viewModels = mActivityTestRule.getActivity().getViewModelProvider().getViewModels(); + + assertThat(viewModels.containsKey(uniqueIdentifierActivity), is(true)); + assertThat(viewModels.containsKey(uniqueIdentifierFragment), is(false)); + } + + @SmallTest + @Test + public void viewModelActivity_fragment_model_state_test() { + mActivityTestRule.launchActivity(VMTestActivity.makeIntent(InstrumentationRegistry.getContext(), false)); + + final int stateValue = 1; + mActivityTestRule.getActivity().getTestFragment().getViewModel().setStateValue(stateValue); + + rotateScreen(1); + + int actualStateValue = mActivityTestRule.getActivity().getTestFragment().getViewModel().getStateValue(); + + assertThat(stateValue, is(actualStateValue)); + } + + @MediumTest + @Test + public void viewModelActivity_instance_count_test() { + mActivityTestRule.launchActivity(VMTestActivity.makeIntent(InstrumentationRegistry.getContext(), false)); + + String uniqueIdentifierActivity = mActivityTestRule.getActivity().getViewModel().getUniqueIdentifier(); + String uniqueIdentifierFragment = mActivityTestRule.getActivity().getTestFragment().getViewModel().getUniqueIdentifier(); + + rotateScreen(5); + + Map> viewModels = + mActivityTestRule.getActivity().getViewModelProvider().getViewModels(); + + assertThat(viewModels.size(), is(2)); //activity + fragment + + assertThat(viewModels.containsKey(uniqueIdentifierActivity), is(true)); + assertThat(viewModels.containsKey(uniqueIdentifierFragment), is(true)); + } + + private void rotateScreen(int numOfTimes) { + for (int i = 0; i < numOfTimes; i++) { + mActivityTestRule.getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + mActivityTestRule.getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + } + } + +} diff --git a/library/src/androidTest/java/eu/inloop/viewmodel/fixture/activity/IVMTestActivityView.java b/library/src/androidTest/java/eu/inloop/viewmodel/fixture/activity/IVMTestActivityView.java new file mode 100644 index 0000000..0d8b0d9 --- /dev/null +++ b/library/src/androidTest/java/eu/inloop/viewmodel/fixture/activity/IVMTestActivityView.java @@ -0,0 +1,9 @@ +package eu.inloop.viewmodel.fixture.activity; + +import eu.inloop.viewmodel.IView; + +public interface IVMTestActivityView extends IView { + + void onLoadData(boolean loaded); + +} diff --git a/library/src/androidTest/java/eu/inloop/viewmodel/fixture/activity/VMTestActivity.java b/library/src/androidTest/java/eu/inloop/viewmodel/fixture/activity/VMTestActivity.java new file mode 100644 index 0000000..0b47177 --- /dev/null +++ b/library/src/androidTest/java/eu/inloop/viewmodel/fixture/activity/VMTestActivity.java @@ -0,0 +1,72 @@ +package eu.inloop.viewmodel.fixture.activity; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.widget.LinearLayout; + +import eu.inloop.viewmodel.base.ViewModelBaseActivity; +import eu.inloop.viewmodel.fixture.fragment.VMTestFragment; + +public class VMTestActivity extends ViewModelBaseActivity implements IVMTestActivityView { + + public static final int RESULT_CODE_OK = 1; + public static final String EXTRA_CALL_ON_BIND = "EXTRA_CALL_ON_BIND"; + + @NonNull + public static Intent makeIntent(@NonNull Context context, boolean callOnBindModel) { + Intent intent = new Intent(context, VMTestActivity.class); + intent.putExtra(EXTRA_CALL_ON_BIND, callOnBindModel); + + return intent; + } + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + LinearLayout view = new LinearLayout(this); + view.setId(android.R.id.content); + setContentView(view); + + if (savedInstanceState == null) { + addTestFragment(); + } + + setModelView(this); + } + + public void addTestFragment() { + getSupportFragmentManager() + .beginTransaction() + .add(android.R.id.content, new VMTestFragment()) + .commitNow(); + } + + public void removeTestFragment() { + getSupportFragmentManager() + .beginTransaction() + .remove(getTestFragment()) + .commitNow(); + } + + @NonNull + public VMTestFragment getTestFragment() { + for (Fragment fragment : getSupportFragmentManager().getFragments()) { + if (fragment instanceof VMTestFragment) { + return (VMTestFragment) fragment; + } + } + throw new AssertionError("Fragment not found"); + } + + @Override + public void onLoadData(boolean loaded) { + setResult(RESULT_CODE_OK); + finish(); + } + +} diff --git a/library/src/androidTest/java/eu/inloop/viewmodel/fixture/activity/VMTestActivityViewModel.java b/library/src/androidTest/java/eu/inloop/viewmodel/fixture/activity/VMTestActivityViewModel.java new file mode 100644 index 0000000..1ec855b --- /dev/null +++ b/library/src/androidTest/java/eu/inloop/viewmodel/fixture/activity/VMTestActivityViewModel.java @@ -0,0 +1,39 @@ +package eu.inloop.viewmodel.fixture.activity; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import eu.inloop.viewmodel.AbstractViewModel; + +public class VMTestActivityViewModel extends AbstractViewModel { + + private boolean mCallOnBind; + + @Override + public void onCreate(@Nullable Bundle arguments, @Nullable Bundle savedInstanceState) { + super.onCreate(arguments, savedInstanceState); + if (arguments == null) { + throw new AssertionError("Arguments must be set for this ViewModel"); + } + mCallOnBind = arguments.getBoolean(VMTestActivity.EXTRA_CALL_ON_BIND); + } + + @Override + public void onBindView(@NonNull IVMTestActivityView view) { + super.onBindView(view); + + if (mCallOnBind) { + loadData(); + } + } + + public void loadData() { + getView().onLoadData(true); + } + + public void loadDataOptional() { + getViewOptional().onLoadData(true); + } + +} diff --git a/library/src/androidTest/java/eu/inloop/viewmodel/fixture/fragment/IVMTestFragmentView.java b/library/src/androidTest/java/eu/inloop/viewmodel/fixture/fragment/IVMTestFragmentView.java new file mode 100644 index 0000000..f8844d4 --- /dev/null +++ b/library/src/androidTest/java/eu/inloop/viewmodel/fixture/fragment/IVMTestFragmentView.java @@ -0,0 +1,9 @@ +package eu.inloop.viewmodel.fixture.fragment; + +import eu.inloop.viewmodel.IView; + +public interface IVMTestFragmentView extends IView { + + void onLoadData(boolean loaded); + +} diff --git a/library/src/androidTest/java/eu/inloop/viewmodel/fixture/fragment/VMTestFragment.java b/library/src/androidTest/java/eu/inloop/viewmodel/fixture/fragment/VMTestFragment.java new file mode 100644 index 0000000..dd03c29 --- /dev/null +++ b/library/src/androidTest/java/eu/inloop/viewmodel/fixture/fragment/VMTestFragment.java @@ -0,0 +1,33 @@ +package eu.inloop.viewmodel.fixture.fragment; + +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; + +import eu.inloop.viewmodel.base.ViewModelBaseFragment; +import eu.inloop.viewmodel.fixture.activity.VMTestActivity; + +public class VMTestFragment extends ViewModelBaseFragment + implements IVMTestFragmentView { + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return new LinearLayout(getContext()); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + setModelView(this); + } + + @Override + public void onLoadData(boolean loaded) { + getActivity().setResult(VMTestActivity.RESULT_CODE_OK); + getActivity().finish(); + } +} diff --git a/library/src/androidTest/java/eu/inloop/viewmodel/fixture/fragment/VMTestFragmentViewModel.java b/library/src/androidTest/java/eu/inloop/viewmodel/fixture/fragment/VMTestFragmentViewModel.java new file mode 100644 index 0000000..6a1e300 --- /dev/null +++ b/library/src/androidTest/java/eu/inloop/viewmodel/fixture/fragment/VMTestFragmentViewModel.java @@ -0,0 +1,42 @@ +package eu.inloop.viewmodel.fixture.fragment; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import eu.inloop.viewmodel.AbstractViewModel; + +public class VMTestFragmentViewModel extends AbstractViewModel { + + private static final String STATE_INT = "STATE_INT"; + + private int mStateValue; + + @Override + public void onCreate(@Nullable Bundle arguments, @Nullable Bundle savedInstanceState) { + super.onCreate(arguments, savedInstanceState); + + if (savedInstanceState != null) { + mStateValue = savedInstanceState.getInt(STATE_INT); + } + } + + @Override + public void onSaveInstanceState(@NonNull Bundle bundle) { + super.onSaveInstanceState(bundle); + bundle.putInt(STATE_INT, mStateValue); + } + + public void setStateValue(int value) { + mStateValue = value; + } + + public int getStateValue() { + return mStateValue; + } + + public void loadData() { + getView().onLoadData(true); + } + +} diff --git a/library/src/androidTest/java/sample/viewmodel/inloop/eu/viewmodelsample/ApplicationTest.java b/library/src/androidTest/java/sample/viewmodel/inloop/eu/viewmodelsample/ApplicationTest.java deleted file mode 100644 index 3844758..0000000 --- a/library/src/androidTest/java/sample/viewmodel/inloop/eu/viewmodelsample/ApplicationTest.java +++ /dev/null @@ -1,13 +0,0 @@ -package sample.viewmodel.inloop.eu.viewmodelsample; - -import android.app.Application; -import android.test.ApplicationTestCase; - -/** - * Testing Fundamentals - */ -public class ApplicationTest extends ApplicationTestCase { - public ApplicationTest() { - super(Application.class); - } -} \ No newline at end of file diff --git a/library/src/main/java/eu/inloop/viewmodel/ViewModelProvider.java b/library/src/main/java/eu/inloop/viewmodel/ViewModelProvider.java index c541de4..c484f72 100644 --- a/library/src/main/java/eu/inloop/viewmodel/ViewModelProvider.java +++ b/library/src/main/java/eu/inloop/viewmodel/ViewModelProvider.java @@ -2,9 +2,12 @@ import android.app.Activity; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; import android.support.v4.app.FragmentActivity; +import java.util.Collections; import java.util.HashMap; +import java.util.Map; /** * Create and keep this class inside your Activity. Store it @@ -49,6 +52,12 @@ public synchronized void removeAllViewModels() { mViewModelCache.clear(); } + @VisibleForTesting + @NonNull + Map> getViewModels() { + return Collections.unmodifiableMap(mViewModelCache); + } + @SuppressWarnings("unchecked") @NonNull public synchronized ViewModelWrapper getViewModel(@NonNull final String modelIdentifier, diff --git a/sample/build.gradle b/sample/build.gradle index 912c8c0..d7bd9f5 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -2,7 +2,7 @@ apply plugin: 'com.android.application' android { compileSdkVersion 26 - buildToolsVersion '25.0.3' + buildToolsVersion '26.0.2' defaultConfig { applicationId 'eu.inloop.viewmodel.sample' @@ -25,15 +25,15 @@ android { } dataBinding { - enabled = true; + enabled = true } } dependencies { - compile 'com.android.support:support-fragment:26.0.1' - compile 'com.android.support:appcompat-v7:26.0.1' - debugCompile 'com.squareup.leakcanary:leakcanary-android:1.4' - releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4' + compile 'com.android.support:support-fragment:26.1.0' + compile 'com.android.support:appcompat-v7:26.1.0' + debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.1' + releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1' compile 'com.jakewharton:butterknife:5.1.2' compile project(':library') }