All Projects → concretesolutions → Kappuccino

concretesolutions / Kappuccino

Licence: apache-2.0
A kotlin library to simplify how to do espresso tests on Android.

Programming Languages

kotlin
9241 projects

Projects that are alternatives of or similar to Kappuccino

marathon
Cross-platform test runner written for Android and iOS projects
Stars: ✭ 398 (+282.69%)
Mutual labels:  instrumentation, espresso
Marathon
Cross-platform test runner written for Android and iOS projects
Stars: ✭ 250 (+140.38%)
Mutual labels:  instrumentation, espresso
Daggermock
A JUnit rule to easily override Dagger 2 objects
Stars: ✭ 1,156 (+1011.54%)
Mutual labels:  espresso
Fluxter
Fast and reliable InfluxDB writer for Elixir
Stars: ✭ 96 (-7.69%)
Mutual labels:  instrumentation
Jest Allure
Generate Allure Report for jest. Allure Report, a flexible lightweight multi-language test report tool with the possibility to add steps, attachments, parameters and so on.
Stars: ✭ 90 (-13.46%)
Mutual labels:  tests
Appmon
Documentation:
Stars: ✭ 1,157 (+1012.5%)
Mutual labels:  instrumentation
Csi Test
CSI test frameworks
Stars: ✭ 90 (-13.46%)
Mutual labels:  tests
Brave Opentracing
Bridge between OpenTracing and Brave
Stars: ✭ 64 (-38.46%)
Mutual labels:  instrumentation
Telemetry poller
Periodically gather measurements and publish them as Telemetry events
Stars: ✭ 101 (-2.88%)
Mutual labels:  instrumentation
Newspaper
An aggregated newspaper app containing news from 10+ local news publishers in Hong Kong. Made with ❤
Stars: ✭ 82 (-21.15%)
Mutual labels:  espresso
Capture Thread
Lock-free framework for loggers, tracers, and mockers in multithreaded C++ programs.
Stars: ✭ 93 (-10.58%)
Mutual labels:  instrumentation
Awesome Playwright
A curated list of awesome tools, utils and projects using Playwright
Stars: ✭ 79 (-24.04%)
Mutual labels:  tests
Openjdk Tests
Home of test infrastructure for AdoptOpenJDK builds
Stars: ✭ 73 (-29.81%)
Mutual labels:  tests
Mvpandroid
Sample app to demonstrate MVP (Model - View - Presenter) architecture in android
Stars: ✭ 91 (-12.5%)
Mutual labels:  espresso
Cubostratus
Blazingly fast Linux syscall collector
Stars: ✭ 68 (-34.62%)
Mutual labels:  instrumentation
Artist
ARTist's core implementation meant to be included in the art project. Provides ARTist APIs and boilerplate code for modules.
Stars: ✭ 97 (-6.73%)
Mutual labels:  instrumentation
Prometheus.cl
Prometheus.io Common Lisp client
Stars: ✭ 67 (-35.58%)
Mutual labels:  instrumentation
Katasuperheroeskotlin
Super Heroes Kata for Android Developers in Kotlin. The main goal is to practice UI Testing.
Stars: ✭ 77 (-25.96%)
Mutual labels:  espresso
Jplusone
Tool for automatic detection and asserting "N+1 SELECT problem" occurences in JPA based Spring Boot Java applications and finding origin of JPA issued SQL statements in general
Stars: ✭ 91 (-12.5%)
Mutual labels:  instrumentation
Mongodb Memory Server
Spinning up mongod in memory for fast tests. If you run tests in parallel this lib helps to spin up dedicated mongodb servers for every test file in MacOS, *nix, Windows or CI environments (in most cases with zero-config).
Stars: ✭ 1,376 (+1223.08%)
Mutual labels:  tests

codebeat badge bitrise badge Download

kappuccino

A framework to simplify the way you do instrumentation tests in your app, using Espresso and Kotlin.

Here is how you do instrumentation tests today, using simply Espresso:

@Test fun loginFieldsAreVisible() {
  onView(withId(R.id.username)).check(matches(isDisplayed())
  onView(withId(R.id.password)).check(matches(isDisplayed())
  onView(withId(R.id.login_button)).check(matches(isDisplayed())
}

This is just to check a simple login screen, and we are not even considering that we may need to scroll to one of these views, due to small screens support.

With scroll, our test will be something like this:

@Test fun loginFieldsAreVisible() {
  onView(withId(R.id.username)).perform(scrollTo()).check(matches(isDisplayed())
  onView(withId(R.id.password)).perform(scrollTo()).check(matches(isDisplayed())
  onView(withId(R.id.login_button)).perform(scrollTo()).check(matches(isDisplayed())
}

We have to repeat a lot of code, this makes the tests hard to read and understand at a first look. Also you may forget some scrollTo(), or mismatch the check function. At the end, all we want to do is check if the views with these ids are displayed.

So, this is how you do the same test with kappuccino library:

@Test fun loginFieldsAreVisible() {
  displayed {
    id(R.id.username)
    id(R.id.password)
    id(R.id.login_button)
  }
}

Cleaner, easier to write and understand. To scroll, all you have to do is pass a parameter to the function:

@Test fun loginFieldsAreVisible() {
  displayed(scroll = true) {
    id(R.id.username)
    id(R.id.password)
    id(R.id.login_button)
  }
}

Installation

1 - Setup kotlin in your project, see the instructions here

2 - Create a kotlin directory into 'src/androidTest/', check the sample code for reference.

3 - Set you sourceDataSet into your build.gradle file

sourceSets {
    androidTest.java.srcDirs = ['src/androidTest/kotlin']
  }

4 - Add library and dependencies into your build.gradle file and sync

androidTestImplementation 'br.com.concretesolutions:kappuccino:$latest.version'

5 - This library depends on the following libraries:

So, ensure those libraries are also in your dependencies.

androidTestImplementation "com.android.support.test.espresso:espresso-intents:$versions.espresso"
androidTestImplementation "com.android.support.test.espresso:espresso-core:$versions.espresso"
androidTestImplementation "com.android.support.test.espresso:espresso-contrib:$versions.espresso"
androidTestImplementation "com.android.support.test.uiautomator:uiautomator-v18:$versions.uiAutomator"

And you're ready to go!

If you have any module conflicts, try to exclude the conflicting module, for example:

androidTestImplementation('br.com.concretesolutions:kappuccino:$latest.version', {
        exclude group: 'com.android.support'
    })

Assertion methods

These are the methods to make view assertions

checked {}
notChecked {}

clickable {}
notClickable {}

selected {}
notSelected {}

displayed {}
notDisplayed {}

notExist {}

Action methods

These are methods to interact with views

click {}
doubleClick {}
longClick {}

typeText {}
clearText {}

Scroll

The scroll method is now a parameter for all the above methods, the default value is false, for example:

@Test fun scrollToButton_andClick() {
  click(scroll = true) {
    id(R.id.login_button)
  }
}

In this case, it will scroll to the view and click. If you don't provide a parameter, the scroll will not happen.

Combine matchers (Matchers.allOf)

To combine multiple matchers, use the allOf method:

@Test fun scrollToButton_andClick() {
  click(scroll = true) {
    allOf {
        id(R.id.login_button)
        text(R.string.login_button_text)
    }
  }
}

Hierarchy

There are two methods of hierarchy matchers: Parent and Descendant.

Parent

You can use Parent method with two different approaches: block matching or combining.

1 - Block matching:
For block matching, pass the parentId as method parameter.

Then, kappuccino will match all the views inside the block:

@Test fun matchParent_blockMatching_example() {
  displayed {
    parent(R.id.parent) {
        id(R.id.username)
        id(R.id.password)
        id(R.id.login_button)
    }
  }
}

Here, kappuccino will check if all the views (username, password and login_button) are descendant of the declared parent, and are displayed.

For better understanding, the code above is equivalent to the one below, using pure Espresso:

@Test fun matchParent_example() {
    onView(
        allOf(isDescendantOf(withId(R.id.parent)), withId(R.id.username)))
        .check(matches(isDisplayed()))
    onView(
        allOf(isDescendantOf(withId(R.id.parent)), withId(R.id.password)))
        .check(matches(isDisplayed()))
    onView(
        allOf(isDescendantOf(withId(R.id.parent)), withId(R.id.login_button)))
        .check(matches(isDisplayed()))
}

2 - Combination of matchers:
You can use the parent method as a combination of matchers:

@Test fun matchParent_combining_example() {
    displayed {
        allOf {
            parent {
                id(R.id.parent)
            }
            id(R.id.username)
        }
    }
}

Here, you will check if the view with id = R.id.username, and with parent with id = R.id.parent, is displayed

Descendant

It works just like the parent method, for both cases (block matching and combining matchers)

@Test fun descendant_block_example() {
    displayed {
        allOf {
            descendant {
                id(R.id.username)
            }
            id(R.id.parent)
        }
    }
}

Here, we'll check if the parent, with child R.id.username is displayed. Same use for block matching.

RecyclerView

To interact with the recycler view:

@Test fun recyclerView_example() {
    recyclerView(R.id.recycler_view) {
        sizeIs(10)
        atPosition(3) {
            displayed {
                id(R.id.item_description)
                text(R.string.description_text)
                text("Item header text")
            }
        }
    }
}

To type text in a RecyclerView item's EditText:

@Test fun recyclerView_textInput_example() {
    recyclerView(R.id.recycler_view) {
        atPosition(0) {
            typeText(R.id.editText, "Position 0")
        }

        atPosition(1) {
            typeText(R.id.editText, "Position 1")
        }
    }
}

To swipe a RecyclerView's item left or right:

@Test fun recyclerView_swipeLeft_example() {
    recyclerView(R.id.recycler_view() {
        atPosition(0) {
            swipeLeft()
        }

        atPosition(1) {
            swipeRight()
        }
    }
}

Menu and action bar

To interact with the options menu:

@Test
fun whenClickingOnItem1_shouldShowCorrectText() {
    menu {
        onItem(R.string.item_1) {
            click()
        }
    }

    displayed {
        text(R.string.item_1_selected)
    }
}

To interact with the action bar:

@Test
fun whenClickingOnActionBarItem_shouldClearText() {
    menu(openOptionsMenu = false) {
        onActionBarItem(R.id.item_clear) {
            click()
        }
    }

    notDisplayed {
        id(R.id.txt_menu)
    }

Matchers

You can use the following matchers:

fun id(@IdRes viewId: Int)
fun text(@StringRes textId: Int)
fun text(text: String)
fun contentDescription(@StringRes contentDescriptionId: Int)
fun contentDescription(contentDescription: String)
fun image(@DrawableRes imageId: Int)
fun textColor(@ColorRes colorId: Int)
fun parent(@IdRes parentId: Int)
fun descendant(@IdRes descendantId: Int)
fun custom(viewMatcher: Matcher<View>) // Here you can pass a custom matcher

TextInputLayout Matchers

You can match TextInputLayout now:

To check if TextInputLayout has an error text

@Test
fun textInputLayout_hasTextError_example() {
    textInputLayout(R.id.textInputLayout) {
        hasTextError()
    }
}

To check error text with text

@Test
fun textInputLayout_checkTextErrorWithText_example() {
    textInputLayout(R.id.textInputLayout) {
         withTextError("example text error")
    }
}

To check error text with an string resource

@Test
fun textInputLayout_checkTextErrorWithResource_example() {
    textInputLayout(R.id.textInputLayout) {
         withTextError(R.string.textError)
    }
}

Intent Matchers

You can match intents easily now:

@Test
fun intentMatcherTest() {
    val WHATS_PACKAGE_NAME = "com.whatsapp"
    val PLAY_STORE_URL = "https://play.google.com/store/apps/details?id="
    Intents.init()
    matchIntent {
        action(Intent.ACTION_VIEW)
        url(PLAY_STORE_URL + WHATS_PACKAGE_NAME)
        result {
           ok()
        }
    }

    click {
        id(R.id.btn_start_activity)
    }

    matchIntent {
        action(Intent.ACTION_VIEW)
        url(PLAY_STORE_URL + WHATS_PACKAGE_NAME)
    }

    Intents.release()
}

If you use some of the result methods (resultOk, resultCanceled, resultData) it's going to be like use the Espresso intending method. If you DON'T use any of the result methods, it's the same as use the Espresso intended method. The above code it will be something like this, without kappuccino

@Test
fun intentMatcherTest() {
    val WHATS_PACKAGE_NAME = "com.whatsapp"
    val PLAY_STORE_URL = "https://play.google.com/store/apps/details?id="
    Intents.init()

    val matcher = allOf(hasAction(Intent.ACTION_VIEW), hasData(Uri.parse(PLAY_STORE_URL + WHATS_PACKAGE_NAME)))
    val result = ActivityResult(Activity.RESULT_OK, null)
    intending(matcher).respondWith(result);

    click {
        id(R.id.btn_start_activity)
    }

    intended(matcher)

    Intents.release()
}

You can also use a custom intent matcher with the custom method

Runtime permissions

Easily handle runtime permissions

@Test
fun grantContactsPermission() {
    click {
        id(R.id.btn_request_permission)
    }

    runtimePermission(Manifest.permission.READ_CONTACTS) {
        allow()
    }

    displayed {
        text("PERMISSION GRANTED")
    }
}

Background matcher

Check view's background. The background must be VectorDrawable, BitmapDrawable or ColorDrawable

@Test
fun backgroundColorTest() {
    displayed {
        allOf {
            id(R.id.view_background)
            background(R.drawable.ic_android)
        }
    }

    displayed {
        background(R.color.colorAccent)
    }
}

For more examples, please check the sample code.

Wiki: coming soon.

Tip: this framework was based on Robots Pattern. It's a good idea to use this framework in combination with this pattern.

LICENSE

This project is available under Apache Public License version 2.0. See LICENSE.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].