Skip to content

Commit 1dae19d

Browse files
committed
Configured project for Espresso testing
Created mock components to inject mock data for UI testing Added first Espresso test
1 parent 75daca1 commit 1dae19d

File tree

15 files changed

+461
-196
lines changed

15 files changed

+461
-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: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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+
8+
/**
9+
* Created by Tamas_Kozmer on 8/8/2017.
10+
*/
11+
class MockDetailsRepository : DetailsRepository {
12+
override fun getQuestionsByUser(userId: Long, forced: Boolean): Single<List<Question>> {
13+
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
14+
}
15+
16+
override fun getAnswersByUser(userId: Long, forced: Boolean): Single<List<Answer>> {
17+
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
18+
}
19+
20+
override fun getFavoritesByUser(userId: Long, forced: Boolean): Single<List<Question>> {
21+
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
22+
}
23+
24+
override fun getQuestionsById(ids: List<Long>, userId: Long, forced: Boolean): Single<List<Question>> {
25+
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
26+
}
27+
}
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: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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.assertion.ViewAssertions
7+
import android.support.test.espresso.matcher.ViewMatchers
8+
import android.support.test.rule.ActivityTestRule
9+
import android.support.test.runner.AndroidJUnit4
10+
import com.example.tamaskozmer.kotlinrxexample.CustomApplication
11+
import com.example.tamaskozmer.kotlinrxexample.DaggerMockApplicationComponent
12+
import com.example.tamaskozmer.kotlinrxexample.R
13+
import com.example.tamaskozmer.kotlinrxexample.mocks.di.modules.MockApplicationModule
14+
import com.example.tamaskozmer.kotlinrxexample.testutil.RecyclerViewMatcher
15+
import com.example.tamaskozmer.kotlinrxexample.view.activities.MainActivity
16+
import org.junit.Before
17+
import org.junit.Rule
18+
import org.junit.Test
19+
import org.junit.runner.RunWith
20+
21+
22+
/**
23+
* Created by Tamas_Kozmer on 8/8/2017.
24+
*/
25+
@RunWith(AndroidJUnit4::class)
26+
class UserListFragmentTest {
27+
28+
@Rule @JvmField
29+
var activityRule = ActivityTestRule(MainActivity::class.java, true, false)
30+
31+
fun withRecyclerView(recyclerViewId: Int): RecyclerViewMatcher {
32+
return RecyclerViewMatcher(recyclerViewId)
33+
}
34+
35+
@Before
36+
fun setUp() {
37+
val instrumentation = InstrumentationRegistry.getInstrumentation()
38+
val app = instrumentation.targetContext.applicationContext as CustomApplication
39+
40+
val testComponent = DaggerMockApplicationComponent.builder()
41+
.mockApplicationModule(MockApplicationModule())
42+
.build()
43+
app.component = testComponent
44+
45+
activityRule.launchActivity(Intent())
46+
}
47+
48+
@Test
49+
fun testRecyclerViewShowingCorrectItems() {
50+
Espresso.onView(withRecyclerView(R.id.recyclerView).atPositionOnView(0, R.id.name))
51+
.check(ViewAssertions.matches(ViewMatchers.withText("User 1")))
52+
53+
Espresso.onView(withRecyclerView(R.id.recyclerView).atPositionOnView(3, R.id.name))
54+
.check(ViewAssertions.matches(ViewMatchers.withText("User 4")))
55+
}
56+
}

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)