Skip to content

Commit e05c3ea

Browse files
committed
Merge branch 'ui_tests' into develop
2 parents cf86cca + 40649fe commit e05c3ea

File tree

15 files changed

+503
-196
lines changed

15 files changed

+503
-196
lines changed

app/build.gradle

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,12 @@ dependencies {
5151
androidTestImplementation ('com.android.support.test.espresso:espresso-core:2.2.2', {
5252
exclude group: 'com.android.support', module: 'support-annotations'
5353
})
54+
55+
androidTestImplementation('com.android.support.test.espresso:espresso-contrib:2.2') {
56+
// Necessary to avoid version conflicts
57+
exclude group: 'com.android.support', module: 'appcompat'
58+
exclude group: 'com.android.support', module: 'support-v4'
59+
exclude group: 'com.android.support', module: 'support-annotations'
60+
exclude module: 'recyclerview-v7'
61+
}
5462
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.example.tamaskozmer.kotlinrxexample.mocks.di.components
2+
3+
import com.example.tamaskozmer.kotlinrxexample.di.components.ApplicationComponent
4+
import com.example.tamaskozmer.kotlinrxexample.mocks.di.modules.MockApplicationModule
5+
import dagger.Component
6+
import javax.inject.Singleton
7+
8+
/**
9+
* Created by Tamas_Kozmer on 8/8/2017.
10+
*/
11+
@Singleton
12+
@Component(modules = arrayOf(MockApplicationModule::class))
13+
interface MockApplicationComponent : ApplicationComponent
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.example.tamaskozmer.kotlinrxexample.mocks.di.modules
2+
3+
import com.example.tamaskozmer.kotlinrxexample.mocks.model.repositories.MockDetailsRepository
4+
import com.example.tamaskozmer.kotlinrxexample.mocks.model.repositories.MockUserRepository
5+
import com.example.tamaskozmer.kotlinrxexample.model.repositories.DetailsRepository
6+
import com.example.tamaskozmer.kotlinrxexample.model.repositories.UserRepository
7+
import com.example.tamaskozmer.kotlinrxexample.util.AppSchedulerProvider
8+
import com.example.tamaskozmer.kotlinrxexample.util.SchedulerProvider
9+
import dagger.Module
10+
import dagger.Provides
11+
import javax.inject.Singleton
12+
13+
/**
14+
* Created by Tamas_Kozmer on 8/8/2017.
15+
*/
16+
@Module
17+
class MockApplicationModule {
18+
19+
@Provides
20+
@Singleton
21+
fun provideUserRepository() : UserRepository {
22+
return MockUserRepository()
23+
}
24+
25+
@Provides
26+
@Singleton
27+
fun provideDetailsRepository() : DetailsRepository {
28+
return MockDetailsRepository()
29+
}
30+
31+
@Provides
32+
@Singleton
33+
fun provideSchedulerProvider() : SchedulerProvider = AppSchedulerProvider()
34+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.example.tamaskozmer.kotlinrxexample.mocks.model.repositories
2+
3+
import com.example.tamaskozmer.kotlinrxexample.model.entities.Answer
4+
import com.example.tamaskozmer.kotlinrxexample.model.entities.Question
5+
import com.example.tamaskozmer.kotlinrxexample.model.repositories.DetailsRepository
6+
import io.reactivex.Single
7+
import io.reactivex.SingleEmitter
8+
9+
/**
10+
* Created by Tamas_Kozmer on 8/8/2017.
11+
*/
12+
class MockDetailsRepository : DetailsRepository {
13+
override fun getQuestionsByUser(userId: Long, forced: Boolean): Single<List<Question>> {
14+
return createSingle(emptyList())
15+
}
16+
17+
override fun getAnswersByUser(userId: Long, forced: Boolean): Single<List<Answer>> {
18+
return createSingle(emptyList())
19+
}
20+
21+
override fun getFavoritesByUser(userId: Long, forced: Boolean): Single<List<Question>> {
22+
return createSingle(emptyList())
23+
}
24+
25+
override fun getQuestionsById(ids: List<Long>, userId: Long, forced: Boolean): Single<List<Question>> {
26+
return createSingle(emptyList())
27+
}
28+
29+
private fun <T> createSingle(emittedItem: T): Single<T> {
30+
return Single.create { emitter: SingleEmitter<T> -> emitter.onSuccess(emittedItem) }
31+
}
32+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.example.tamaskozmer.kotlinrxexample.mocks.model.repositories
2+
3+
import com.example.tamaskozmer.kotlinrxexample.model.entities.User
4+
import com.example.tamaskozmer.kotlinrxexample.model.entities.UserListModel
5+
import com.example.tamaskozmer.kotlinrxexample.model.repositories.UserRepository
6+
import io.reactivex.Single
7+
import io.reactivex.SingleEmitter
8+
9+
/**
10+
* Created by Tamas_Kozmer on 8/8/2017.
11+
*/
12+
class MockUserRepository : UserRepository {
13+
14+
override fun getUsers(page: Int, forced: Boolean): Single<UserListModel> {
15+
val users = (1..10L).map {
16+
val number = (page - 1) * 10 + it
17+
User(it, "User $number", number * 100, "")
18+
}
19+
20+
return Single.create<UserListModel> { emitter: SingleEmitter<UserListModel> ->
21+
val userListModel = UserListModel(users)
22+
emitter.onSuccess(userListModel)
23+
}
24+
}
25+
}

app/src/androidTest/java/com/example/tamaskozmer/kotlinrxexample/UserDaoTest.kt renamed to app/src/androidTest/java/com/example/tamaskozmer/kotlinrxexample/model/persistence/daos/UserDaoTest.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
package com.example.tamaskozmer.kotlinrxexample
1+
package com.example.tamaskozmer.kotlinrxexample.model.persistence.daos
22

33
import android.arch.persistence.room.Room
44
import android.support.test.InstrumentationRegistry
55
import android.support.test.runner.AndroidJUnit4
66
import com.example.tamaskozmer.kotlinrxexample.model.entities.User
77
import com.example.tamaskozmer.kotlinrxexample.model.persistence.AppDatabase
8-
import com.example.tamaskozmer.kotlinrxexample.model.persistence.daos.UserDao
98
import org.junit.After
109
import org.junit.Assert
1110
import org.junit.Before
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package com.example.tamaskozmer.kotlinrxexample.testutil;
2+
3+
import android.content.res.Resources;
4+
import android.support.v7.widget.RecyclerView;
5+
import android.view.View;
6+
7+
import org.hamcrest.Description;
8+
import org.hamcrest.Matcher;
9+
import org.hamcrest.TypeSafeMatcher;
10+
11+
/**
12+
* Helper class to match RecyclerView items by Danny Roa
13+
* https://github.com/dannyroa/espresso-samples/blob/master/RecyclerView/app/src/androidTest/java/com/dannyroa/espresso_samples/recyclerview/RecyclerViewMatcher.java
14+
* Created by dannyroa on 5/10/15.
15+
*/
16+
public class RecyclerViewMatcher {
17+
private final int recyclerViewId;
18+
19+
public RecyclerViewMatcher(int recyclerViewId) {
20+
this.recyclerViewId = recyclerViewId;
21+
}
22+
23+
public Matcher<View> atPosition(final int position) {
24+
return atPositionOnView(position, -1);
25+
}
26+
27+
public Matcher<View> atPositionOnView(final int position, final int targetViewId) {
28+
29+
return new TypeSafeMatcher<View>() {
30+
Resources resources = null;
31+
View childView;
32+
33+
public void describeTo(Description description) {
34+
String idDescription = Integer.toString(recyclerViewId);
35+
if (this.resources != null) {
36+
try {
37+
idDescription = this.resources.getResourceName(recyclerViewId);
38+
} catch (Resources.NotFoundException var4) {
39+
idDescription = String.format("%s (resource name not found)", recyclerViewId);
40+
}
41+
}
42+
43+
description.appendText("with id: " + idDescription);
44+
}
45+
46+
public boolean matchesSafely(View view) {
47+
48+
this.resources = view.getResources();
49+
50+
if (childView == null) {
51+
RecyclerView recyclerView =
52+
view.getRootView().findViewById(recyclerViewId);
53+
if (recyclerView != null && recyclerView.getId() == recyclerViewId) {
54+
childView = recyclerView.findViewHolderForAdapterPosition(position).itemView;
55+
} else {
56+
return false;
57+
}
58+
}
59+
60+
if (targetViewId == -1) {
61+
return view == childView;
62+
} else {
63+
View targetView = childView.findViewById(targetViewId);
64+
return view == targetView;
65+
}
66+
67+
}
68+
};
69+
}
70+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package com.example.tamaskozmer.kotlinrxexample.view.fragments
2+
3+
import android.content.Intent
4+
import android.support.test.InstrumentationRegistry
5+
import android.support.test.espresso.Espresso
6+
import android.support.test.espresso.action.ViewActions
7+
import android.support.test.espresso.assertion.ViewAssertions
8+
import android.support.test.espresso.contrib.RecyclerViewActions
9+
import android.support.test.espresso.matcher.ViewMatchers
10+
import android.support.test.rule.ActivityTestRule
11+
import android.support.test.runner.AndroidJUnit4
12+
import android.support.v7.widget.RecyclerView
13+
import com.example.tamaskozmer.kotlinrxexample.CustomApplication
14+
import com.example.tamaskozmer.kotlinrxexample.R
15+
import com.example.tamaskozmer.kotlinrxexample.mocks.di.components.DaggerMockApplicationComponent
16+
import com.example.tamaskozmer.kotlinrxexample.mocks.di.modules.MockApplicationModule
17+
import com.example.tamaskozmer.kotlinrxexample.testutil.RecyclerViewMatcher
18+
import com.example.tamaskozmer.kotlinrxexample.view.activities.MainActivity
19+
import org.junit.Before
20+
import org.junit.Rule
21+
import org.junit.Test
22+
import org.junit.runner.RunWith
23+
24+
25+
/**
26+
* Created by Tamas_Kozmer on 8/8/2017.
27+
*/
28+
@RunWith(AndroidJUnit4::class)
29+
class UserListFragmentTest {
30+
31+
@Rule @JvmField
32+
var activityRule = ActivityTestRule(MainActivity::class.java, true, false)
33+
34+
private fun withRecyclerView(recyclerViewId: Int): RecyclerViewMatcher {
35+
return RecyclerViewMatcher(recyclerViewId)
36+
}
37+
38+
@Before
39+
fun setUp() {
40+
val instrumentation = InstrumentationRegistry.getInstrumentation()
41+
val app = instrumentation.targetContext.applicationContext as CustomApplication
42+
43+
val testComponent = DaggerMockApplicationComponent.builder()
44+
.mockApplicationModule(MockApplicationModule())
45+
.build()
46+
app.component = testComponent
47+
48+
activityRule.launchActivity(Intent())
49+
}
50+
51+
@Test
52+
fun testRecyclerViewShowingCorrectItems() {
53+
checkNameOnPosition(0, "User 1")
54+
checkNameOnPosition(2, "User 3")
55+
}
56+
57+
@Test
58+
fun testRecyclerViewShowingCorrectItemsAfterScroll() {
59+
Espresso.onView(ViewMatchers.withId(R.id.recyclerView))
60+
.perform(RecyclerViewActions.scrollToPosition<RecyclerView.ViewHolder>(8))
61+
62+
checkNameOnPosition(8, "User 9")
63+
}
64+
65+
@Test
66+
fun testRecyclerViewShowingCorrectItemsAfterPagination() {
67+
// Trigger pagination
68+
Espresso.onView(ViewMatchers.withId(R.id.recyclerView))
69+
.perform(RecyclerViewActions.scrollToPosition<RecyclerView.ViewHolder>(9))
70+
71+
// Scroll to a position on the next page
72+
Espresso.onView(ViewMatchers.withId(R.id.recyclerView))
73+
.perform(RecyclerViewActions.scrollToPosition<RecyclerView.ViewHolder>(15))
74+
75+
// Check if view is showing the correct text
76+
checkNameOnPosition(15, "User 16")
77+
}
78+
79+
private fun checkNameOnPosition(position: Int, expectedName: String) {
80+
Espresso.onView(withRecyclerView(R.id.recyclerView).atPositionOnView(position, R.id.name))
81+
.check(ViewAssertions.matches(ViewMatchers.withText(expectedName)))
82+
}
83+
84+
@Test
85+
fun testOpenDetailsOnItemClick() {
86+
Espresso.onView(ViewMatchers.withId(R.id.recyclerView))
87+
.perform(RecyclerViewActions.actionOnItemAtPosition<RecyclerView.ViewHolder>(0, ViewActions.click()))
88+
89+
// Check if we see the new recyclerview with the clicked items name shown in the first element
90+
Espresso.onView(withRecyclerView(R.id.detailsRecyclerView).atPositionOnView(0, R.id.name))
91+
.check(ViewAssertions.matches(ViewMatchers.withText("User 1")))
92+
}
93+
}

app/src/main/java/com/example/tamaskozmer/kotlinrxexample/CustomApplication.kt

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,21 @@ import com.facebook.stetho.Stetho
1111
*/
1212
class CustomApplication : Application() {
1313

14-
val component: ApplicationComponent by lazy {
15-
DaggerApplicationComponent
16-
.builder()
17-
.applicationModule(ApplicationModule(this))
18-
.build()
19-
}
14+
lateinit var component: ApplicationComponent
2015

2116
override fun onCreate() {
2217
super.onCreate()
18+
19+
initAppComponent()
20+
2321
Stetho.initializeWithDefaults(this);
2422
component.inject(this)
2523
}
24+
25+
private fun initAppComponent() {
26+
component = DaggerApplicationComponent
27+
.builder()
28+
.applicationModule(ApplicationModule(this))
29+
.build()
30+
}
2631
}

app/src/main/java/com/example/tamaskozmer/kotlinrxexample/di/modules/ApplicationModule.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import android.arch.persistence.room.Room
44
import android.content.Context
55
import com.example.tamaskozmer.kotlinrxexample.CustomApplication
66
import com.example.tamaskozmer.kotlinrxexample.model.persistence.AppDatabase
7+
import com.example.tamaskozmer.kotlinrxexample.model.repositories.DefaultDetailsRepository
8+
import com.example.tamaskozmer.kotlinrxexample.model.repositories.DefaultUserRepository
79
import com.example.tamaskozmer.kotlinrxexample.model.repositories.DetailsRepository
810
import com.example.tamaskozmer.kotlinrxexample.model.repositories.UserRepository
911
import com.example.tamaskozmer.kotlinrxexample.model.services.QuestionService
@@ -41,7 +43,7 @@ class ApplicationModule(val application: CustomApplication) {
4143
@Singleton
4244
fun provideUserRepository(retrofit: Retrofit, database: AppDatabase, connectionHelper: ConnectionHelper,
4345
preferencesHelper: PreferencesHelper, calendarWrapper: CalendarWrapper): UserRepository {
44-
return UserRepository(
46+
return DefaultUserRepository(
4547
retrofit.create(UserService::class.java),
4648
database.userDao(),
4749
connectionHelper,
@@ -53,7 +55,7 @@ class ApplicationModule(val application: CustomApplication) {
5355
@Singleton
5456
fun provideDetailsRepository(retrofit: Retrofit, database: AppDatabase, connectionHelper: ConnectionHelper,
5557
preferencesHelper: PreferencesHelper, calendarWrapper: CalendarWrapper) : DetailsRepository {
56-
return DetailsRepository(
58+
return DefaultDetailsRepository(
5759
retrofit.create(UserService::class.java),
5860
retrofit.create(QuestionService::class.java),
5961
database.questionDao(),

0 commit comments

Comments
 (0)