Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 0adbd98
Showing
94 changed files
with
6,663 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
*.iml | ||
.idea | ||
.gradle | ||
/local.properties | ||
.DS_Store | ||
build/ | ||
/captures | ||
.externalNativeBuild | ||
app/build | ||
build |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
Github Browser Sample with Android Architecture Components | ||
|
||
This is a sample app that uses Android Architecture Components with Dagger 2. | ||
|
||
**NOTE** It is a relatively more complex and complete example so if you are not familiar | ||
with [Architecture Components][arch], you are highly recommended to check other examples | ||
in this repository first. | ||
|
||
## Functionality | ||
The app is composed of 3 main screens. | ||
### SearchFragment | ||
Allows you to search repositories on Github. | ||
Each search result is kept in the database in `RepoSearchResult` table where | ||
the list of repository IDs are denormalized into a single column. | ||
The actual `Repo` instances live in the `Repo` table. | ||
|
||
Each time a new page is fetched, the same `RepoSearchResult` record in the | ||
Database is updated with the new list of repository ids. | ||
|
||
**NOTE** The UI currently loads all `Repo` items at once, which would not | ||
perform well on lower end devices. Instead of manually writing lazy | ||
adapters, we've decided to wait until the built in support in Room is released. | ||
|
||
### RepoFragment | ||
This fragment displays the details of a repository and its contributors. | ||
### UserFragment | ||
This fragment displays a user and their repositories. | ||
|
||
## Building | ||
You can open the project in Android studio and press run. | ||
## Testing | ||
The project uses both instrumentation tests that run on the device | ||
and local unit tests that run on your computer. | ||
To run both of them and generate a coverage report, you can run: | ||
|
||
`./gradlew fullCoverageReport` (requires a connected device or an emulator) | ||
|
||
### Device Tests | ||
#### UI Tests | ||
The projects uses Espresso for UI testing. Since each fragment | ||
is limited to a ViewModel, each test mocks related ViewModel to | ||
run the tests. | ||
#### Database Tests | ||
The project creates an in memory database for each database test but still | ||
runs them on the device. | ||
|
||
### Local Unit Tests | ||
#### ViewModel Tests | ||
Each ViewModel is tested using local unit tests with mock Repository | ||
implementations. | ||
#### Repository Tests | ||
Each Repository is tested using local unit tests with mock web service and | ||
mock database. | ||
#### Webservice Tests | ||
The project uses [MockWebServer][mockwebserver] project to test REST api interactions. | ||
|
||
|
||
## Libraries | ||
* [Android Support Library][support-lib] | ||
* [Android Architecture Components][arch] | ||
* [Android Data Binding][data-binding] | ||
* [Dagger 2][dagger2] for dependency injection | ||
* [Retrofit][retrofit] for REST api communication | ||
* [Glide][glide] for image loading | ||
* [Timber][timber] for logging | ||
* [espresso][espresso] for UI tests | ||
* [mockito][mockito] for mocking in tests | ||
|
||
|
||
[mockwebserver]: https://github.com/square/okhttp/tree/master/mockwebserver | ||
[support-lib]: https://developer.android.com/topic/libraries/support-library/index.html | ||
[arch]: https://developer.android.com/arch | ||
[data-binding]: https://developer.android.com/topic/libraries/data-binding/index.html | ||
[espresso]: https://google.github.io/android-testing-support-library/docs/espresso/ | ||
[dagger2]: https://google.github.io/dagger | ||
[retrofit]: http://square.github.io/retrofit | ||
[glide]: https://github.com/bumptech/glide | ||
[timber]: https://github.com/JakeWharton/timber | ||
[mockito]: http://site.mockito.org |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
*.iml | ||
.gradle | ||
/local.properties | ||
/.idea | ||
/.idea/workspace.xml | ||
/.idea/libraries | ||
.DS_Store | ||
/build | ||
/captures |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
/* | ||
* Copyright (C) 2017 The Android Open Source Project | ||
* | ||
* 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. | ||
*/ | ||
|
||
apply plugin: 'com.android.application' | ||
apply plugin: "io.mironov.smuggler" | ||
apply plugin: 'kotlin-android' | ||
apply plugin: 'kotlin-kapt' | ||
apply plugin: 'jacoco' | ||
android { | ||
compileSdkVersion 25 | ||
buildToolsVersion "25.0.3" | ||
defaultConfig { | ||
applicationId "it.codingjam.github" | ||
minSdkVersion 16 | ||
targetSdkVersion 25 | ||
versionCode 1 | ||
versionName "1.0" | ||
testInstrumentationRunner 'com.github.tmurakami.dexopener.DexOpenerAndroidJUnitRunner' | ||
} | ||
buildTypes { | ||
debug { | ||
testCoverageEnabled !project.hasProperty('android.injected.invoked.from.ide') | ||
} | ||
release { | ||
minifyEnabled false | ||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' | ||
} | ||
} | ||
dataBinding { | ||
enabled = true | ||
} | ||
compileOptions { | ||
sourceCompatibility JavaVersion.VERSION_1_7 | ||
targetCompatibility JavaVersion.VERSION_1_7 | ||
} | ||
sourceSets { | ||
androidTest.java.srcDirs += "src/test-common/java" | ||
test.java.srcDirs += "src/test-common/java" | ||
} | ||
lintOptions { | ||
disable 'GoogleAppIndexingWarning' | ||
} | ||
} | ||
|
||
jacoco { | ||
toolVersion = "0.7.4+" | ||
} | ||
|
||
dependencies { | ||
kapt 'com.android.databinding:compiler:2.3.3' | ||
compile "com.android.support:appcompat-v7:$support_version" | ||
compile "com.android.support:recyclerview-v7:$support_version" | ||
compile "com.android.support:cardview-v7:$support_version" | ||
compile "com.android.support:design:$support_version" | ||
compile "android.arch.lifecycle:runtime:$arch_version" | ||
compile "android.arch.lifecycle:extensions:$arch_version" | ||
compile "com.squareup.retrofit2:retrofit:$retrofit_version" | ||
compile "com.squareup.retrofit2:converter-gson:$retrofit_version" | ||
compile "com.squareup.retrofit2:adapter-rxjava2:$retrofit_version" | ||
compile "com.squareup.okhttp3:logging-interceptor:$okhttp_version" | ||
compile "com.github.bumptech.glide:glide:$glide_version" | ||
|
||
compile "com.google.dagger:dagger:$dagger_version" | ||
compile "com.android.support.constraint:constraint-layout:$constraint_layout_version" | ||
|
||
compile "com.jakewharton.timber:timber:$timber_version" | ||
|
||
compile 'io.reactivex.rxjava2:rxandroid:2.0.1' | ||
compile 'io.reactivex.rxjava2:rxjava:2.1.0' | ||
compile 'io.reactivex.rxjava2:rxkotlin:2.0.3' | ||
|
||
kapt "com.google.dagger:dagger-android-processor:$dagger_version" | ||
kapt "com.google.dagger:dagger-compiler:$dagger_version" | ||
kapt "android.arch.lifecycle:compiler:$arch_version" | ||
|
||
testCompile "junit:junit:$junit_version" | ||
testCompile "com.squareup.okhttp3:mockwebserver:$okhttp_version" | ||
testCompile ("android.arch.core:core-testing:$arch_version", { | ||
exclude group: 'com.android.support', module: 'support-compat' | ||
exclude group: 'com.android.support', module: 'support-annotations' | ||
exclude group: 'com.android.support', module: 'support-core-utils' | ||
}) | ||
testCompile "org.mockito:mockito-core:$mockito_version" | ||
//noinspection GradleDependency | ||
testCompile 'org.assertj:assertj-core:2.5.0' | ||
testCompile 'com.github.fabioCollini:DaggerMock:c36fc62ccd' | ||
|
||
androidTestCompile "com.android.support:appcompat-v7:$support_version" | ||
androidTestCompile "com.android.support:recyclerview-v7:$support_version" | ||
androidTestCompile "com.android.support:support-v4:$support_version" | ||
androidTestCompile "com.android.support:design:$support_version" | ||
|
||
androidTestCompile("com.android.support.test.espresso:espresso-core:$espresso_version", { | ||
exclude group: 'com.android.support', module: 'support-annotations' | ||
exclude group: 'com.google.code.findbugs', module: 'jsr305' | ||
}) | ||
androidTestCompile("com.android.support.test.espresso:espresso-contrib:$espresso_version", { | ||
exclude group: 'com.android.support', module: 'support-annotations' | ||
exclude group: 'com.google.code.findbugs', module: 'jsr305' | ||
}) | ||
|
||
androidTestCompile("android.arch.core:core-testing:$arch_version", { | ||
}) | ||
androidTestCompile 'com.github.tmurakami:dexopener:0.9.8' | ||
androidTestCompile "org.mockito:mockito-android:$mockito_version" | ||
androidTestCompile('com.nhaarman:mockito-kotlin-kt1.1:1.5.0') { | ||
exclude group: 'org.jetbrains.kotlin' | ||
} | ||
androidTestCompile 'com.github.fabioCollini:DaggerMock:c36fc62ccd' | ||
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" | ||
} | ||
|
||
task fullCoverageReport(type: JacocoReport) { | ||
dependsOn 'createDebugCoverageReport' | ||
dependsOn 'testDebugUnitTest' | ||
reports { | ||
xml.enabled = true | ||
html.enabled = true | ||
} | ||
|
||
def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', | ||
'**/*Test*.*', 'android/**/*.*', | ||
'com/android/databinding/**/*.class', | ||
'**/databinding/*.class', | ||
'**/*AutoValue_*.class', | ||
'**/*_MembersInjector.class', | ||
'**/Dagger*Component.class', | ||
'**/Dagger*Component$Builder.class', | ||
'**/*_*Factory.class', | ||
'**/*ComponentImpl.class', | ||
'**/*SubComponentBuilder.class'] | ||
def debugTree = fileTree(dir: "${buildDir}/intermediates/classes/debug", excludes: fileFilter) | ||
def mainSrc = "${project.projectDir}/src/main/java" | ||
|
||
sourceDirectories = files([mainSrc]) | ||
classDirectories = files([debugTree]) | ||
executionData = fileTree(dir: "$buildDir", includes: [ | ||
"jacoco/testDebugUnitTest.exec", | ||
"outputs/code-coverage/connected/*coverage.ec" | ||
]) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# Add project specific ProGuard rules here. | ||
# By default, the flags in this file are appended to flags specified | ||
# in /Users/yboyar/android/sdk/tools/proguard/proguard-android.txt | ||
# You can edit the include path and order by changing the proguardFiles | ||
# directive in build.gradle. | ||
# | ||
# For more details, see | ||
# http://developer.android.com/guide/developing/tools/proguard.html | ||
|
||
# Add any project specific keep options here: | ||
|
||
# If your project uses WebView with JS, uncomment the following | ||
# and specify the fully qualified class name to the JavaScript interface | ||
# class: | ||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview { | ||
# public *; | ||
#} | ||
|
||
# Uncomment this to preserve the line number information for | ||
# debugging stack traces. | ||
#-keepattributes SourceFile,LineNumberTable | ||
|
||
# If you keep the line number information, uncomment this to | ||
# hide the original source file name. | ||
#-renamesourcefileattribute SourceFile |
98 changes: 98 additions & 0 deletions
98
app/src/androidTest/java/it/codingjam/github/ui/repo/RepoFragmentTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
/* | ||
* Copyright (C) 2017 The Android Open Source Project | ||
* | ||
* 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 it.codingjam.github.ui.repo | ||
|
||
import android.support.annotation.StringRes | ||
import android.support.test.InstrumentationRegistry | ||
import android.support.test.espresso.Espresso.onView | ||
import android.support.test.espresso.assertion.ViewAssertions.matches | ||
import android.support.test.espresso.matcher.ViewMatchers.* | ||
import android.view.View | ||
import com.nhaarman.mockito_kotlin.any | ||
import com.nhaarman.mockito_kotlin.eq | ||
import it.codingjam.github.NavigationController | ||
import it.codingjam.github.R | ||
import it.codingjam.github.util.FragmentTestRule | ||
import it.codingjam.github.util.GitHubDaggerMockRule | ||
import it.codingjam.github.util.TestData.Companion.CONTRIBUTOR1 | ||
import it.codingjam.github.util.TestData.Companion.CONTRIBUTOR2 | ||
import it.codingjam.github.util.TestData.Companion.OWNER | ||
import it.codingjam.github.util.TestData.Companion.REPO_1 | ||
import it.codingjam.github.vo.Contributor | ||
import it.codingjam.github.vo.RepoDetail | ||
import it.codingjam.github.vo.RepoId | ||
import it.codingjam.github.vo.Resource | ||
import org.hamcrest.Matchers.not | ||
import org.junit.Rule | ||
import org.junit.Test | ||
import org.mockito.BDDMockito.willAnswer | ||
import org.mockito.Mock | ||
import java.util.* | ||
|
||
class RepoFragmentTest { | ||
|
||
@get:Rule var fragmentRule = FragmentTestRule() | ||
|
||
@get:Rule var daggerMockRule = GitHubDaggerMockRule() | ||
|
||
@Mock lateinit var viewModel: RepoViewModel | ||
|
||
@Mock lateinit var navigationController: NavigationController | ||
|
||
@Test fun testLoading() { | ||
val fragment = RepoFragment.create(RepoId("a", "b")) | ||
setState( | ||
fragment as RepoFragment, | ||
RepoViewState(Resource.Empty), | ||
RepoViewState(Resource.Loading) | ||
) | ||
|
||
fragmentRule.launchFragment(fragment) | ||
|
||
onView(withId(R.id.progress_bar)).check(matches(isDisplayed())) | ||
onView(withId(R.id.retry)).check(matches(not<View>(isDisplayed()))) | ||
} | ||
|
||
@Test fun testValueWhileLoading() { | ||
val fragment = RepoFragment.create(RepoId("a", "b")) | ||
setState(fragment as RepoFragment, | ||
RepoViewState(Resource.Empty), | ||
RepoViewState(Resource.Loading), | ||
RepoViewState(Resource.Success(RepoDetail(REPO_1, Arrays.asList<Contributor>(CONTRIBUTOR1, CONTRIBUTOR2)))) | ||
) | ||
|
||
fragmentRule.launchFragment(fragment) | ||
|
||
onView(withId(R.id.progress_bar)).check(matches(not<View>(isDisplayed()))) | ||
onView(withId(R.id.name)).check(matches( | ||
withText(getString(R.string.repo_full_name, OWNER.login, REPO_1.name)))) | ||
onView(withId(R.id.description)).check(matches(withText(REPO_1.description))) | ||
} | ||
|
||
private fun setState(fragment: RepoFragment, vararg states: RepoViewState) = | ||
willAnswer { invocation -> | ||
val observer = invocation.getArgument<(RepoViewState) -> Unit>(1) | ||
for (viewState in states) { | ||
observer(viewState) | ||
} | ||
null | ||
}.given(viewModel).observe(eq(fragment), any()) | ||
|
||
private fun getString(@StringRes id: Int, vararg args: Any): String { | ||
return InstrumentationRegistry.getTargetContext().getString(id, *args) | ||
} | ||
} |
15 changes: 15 additions & 0 deletions
15
app/src/androidTest/java/it/codingjam/github/util/FragmentTestRule.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package it.codingjam.github.util | ||
|
||
|
||
import android.support.test.rule.ActivityTestRule | ||
import android.support.v4.app.Fragment | ||
|
||
import it.codingjam.github.testing.SingleFragmentActivity | ||
|
||
class FragmentTestRule : ActivityTestRule<SingleFragmentActivity>(SingleFragmentActivity::class.java, false, false) { | ||
|
||
fun launchFragment(fragment: Fragment) { | ||
launchActivity(null) | ||
activity.setFragment(fragment) | ||
} | ||
} |
Oops, something went wrong.