Testing strategies for the apps

Automated testing in software development is a great tool to ensure high quality solutions, especially when maintaining an application over a long period. Testing in our apps helps us cover cases that would take a very long time to test manually. You have mobile devices with many different versions of the operating system, many different screen sizes and vendor specific implementations that differs from standard operating system behavior, just to name a few.

Since we began writing tests at a larger scale for the apps the number of bugs has gone down significantly while the end user satisfaction has increased. This increases trust in our solutions which allows us to build long standing business relationships with our partners.

The purpose of this article is to explain how we view and are working with automated testing in our mobile apps.

Types of tests

Unit tests

In our apps unit tests covers logic such as filtering of data from backend, ensuring translation text keys have a value and input validators.

Unit tests do not require an emulator to run since they only have dependencies on code specific implementations and the rest is mocked away.

@Test
fun testNameToInitial() {
var participant = VisibaParticipant.getDefault(displayName = "Jan")
Assert.assertEquals(participant.getNameInitals(), "J")

participant = VisibaParticipant.getDefault(displayName = "Jan ")
Assert.assertEquals(participant.getNameInitals(), "J")

participant = VisibaParticipant.getDefault(displayName = "Jan-Erik Gode")
Assert.assertEquals(participant.getNameInitals(), "JG")

participant = VisibaParticipant.getDefault(displayName = "")
Assert.assertEquals(participant.getNameInitals(), "?")

participant = VisibaParticipant.getDefault(displayName = " ")
Assert.assertEquals(participant.getNameInitals(), "?")

participant = VisibaParticipant.getDefault(displayName = "Jan Turesson")
Assert.assertEquals(participant.getNameInitals(), "JT")
}

Example of one our unit tests

Integration tests

These tests are a great compliment to the regression testing that is performed by QA before each new release of the apps. When done right they can catch subtle bugs long before they become an issue.

For the apps integration tests can take a long time to run because they often contain a lot of code that must run on an emulator.

In our apps integration tests covers major flows like booking a timeslot with everything except API-calls being non-mocked code.

@Test
fun testEntireTimeslotBookingFlow() {
initTestVariant()

testRobot
.clickBookAppointmentButton()
timeslotGroupActivityTestRobot
.selectTimeslotByResourceName("name")
.clickNextButton()
timeslotBookingActivityTestRobot
.setReasonText("reason")
.clickBookButton()
timeslotBookingConfirmationActivityTestRobot
.scrollToConfirmButton()
.clickConfirmButton()
appointmentActivityTestRobot
.clickStartAppointmentButton()
waitingRooActivityTestRobot
.clickAnswerCall()
videoCallActivityTestRobot
.clickExpandPortMenu()
.clickLeaveCall()
.clickYesInLeaveCallDialog()
}

Example of an integration test from the apps landing page all the way to a video call

UI-tests

These tests are most useful when we need to validate that a view looks correct according to the current configuration. The configuration might contain a flag that says if we should show/hide some elements in the interface. Manually testing all different combinations of configuration flags quickly becomes unfeasible and therefore automating it saves us time.

@Test
fun canFillAndSubmitSurvey() {
initActivityVariant(SurveyViewTestVariant.Standard())

testRobot
.setFreeStyleText("text")
.clickSurveyStar2()
.clickSubmitSurvey()
}

Example of a UI-test limited to the scope of one view

Why tests?

Functional documentation

Edge cases

Refactoring safety harness

Faster debugging

This applies to debugging also. If you know in which class or function that bug occurs, you can write tests to force the bugged state and quickly validate that your solution solved it.

Architecture

Robot pattern

@Test
fun testCanCancelBankIdLogin() {
initActivityVariant(LoginViewTestVariant.LoginViewBankIdPollingTestVariant())

testRobot
.selectBankIdAuthMethod()
.setNationalIdNumber("XXXXXXXXX")
.clickBankIdLogin()
.clickCancelLoading()
.clickBankIdLogin()
}

It is very easy for someone unfamiliar with the login flow to follow what is happening in this test thanks to the test robot. The test robot is also very easy to reuse in integration tests which chains multiple test robots together when testing flows.

Test variants

Dependency injection

Dependency injection also helps making code that normally would not be testable by discouraging the use of things like global static helper classes. For example, in a function that writes a file to disk you can mock the file-writing so you can check how it handles error or success handling rather than the actual writing itself.

Writing and running tests

Avoid happy paths

Continuous integration

Example of a pull request in Github where the build has failed

Multiple devices and configs

Summary

Philip Sandegren
App Developer at Visiba Care

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store