Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
fabioCollini committed Jun 13, 2017
0 parents commit 0adbd98
Show file tree
Hide file tree
Showing 94 changed files with 6,663 additions and 0 deletions.
10 changes: 10 additions & 0 deletions .gitignore
@@ -0,0 +1,10 @@
*.iml
.idea
.gradle
/local.properties
.DS_Store
build/
/captures
.externalNativeBuild
app/build
build
79 changes: 79 additions & 0 deletions README.md
@@ -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
9 changes: 9 additions & 0 deletions app/.gitignore
@@ -0,0 +1,9 @@
*.iml
.gradle
/local.properties
/.idea
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
154 changes: 154 additions & 0 deletions app/build.gradle
@@ -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"
])
}
25 changes: 25 additions & 0 deletions app/proguard-rules.pro
@@ -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
@@ -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)
}
}
@@ -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)
}
}

0 comments on commit 0adbd98

Please sign in to comment.