[MUSIC PLAYING]
JOLANDA VERHOEF: Hi.
I'm Jolanda.
And I'm an engineer on the Developer Relations team.
Today, I'll show you how to write your first UI
test using Jetpack Compose.
We will test this calculator app and, on the way,
learn about different testing APIs.
We will fill in a formula and try
to break the calculator by entering an invalid formula.
Let's get started.
Before we can write any tests, we
need to go to the app's build.gradle file
and add the required library dependencies.
Then we right-click our Android test source directory,
choose New, and create a Kotlin Class.
We'll call it CalculatorTest.
In the newly created class, we add a JUnit test rule.
This test rule allows us to set the test content and interact
with our app inside the test.
A test class should always have a test rule defined.
We also add a method with a test annotation
to indicate that it should be run as part of our test suite.
In the test, we use a test rule setContent method
to set the content of our screen to be our calculator.
In here we're in a composable context,
so we can call any composable functions needed
to set up our test scenario.
If we run this test, we will already
see the calculator in action.
We can also see that our test succeeds.
However, it's still not doing much.
And we didn't actually check anything.
So let's change that.
Our test rule contains different finder methods
to search for elements on our screen.
In Compose, elements on the screen
are represented by nodes.
So in our test, we can search for a node with a text string
1.
In our calculator, this will match the elements
with the number 1 on it.
When you find a node, you can call the different perform
methods to perform actions on that node.
In our case, let's click on the nodes.
If we now run our test, it successfully finds and clicks
the number 1.
Amazing.
Let's make our formula a bit more complicated
and add some more clicks.
When we now run our test, it correctly
clicks on 1 plus 2 plus--
and wait.
Clicking the number 3 fails.
Why is that?
Looking at the error message, we can
see that our test found two nodes that have the text 3.
Not only our button is matching, but also the result
of the current calculation.
We can fix this issue by creating
a more complex matcher.
Instead of using onNodeWithText, we use the more generic onNode.
In it, we can use one of the has methods to create our matcher.
We still want our element to have the text 3,
so let's start with the hasText matcher.
Then we can use the and operator to add another matcher.
In our case, we can use the hasClick action
to only match clickable nodes.
Our button will match these requirements,
but our result won't, as it's not clickable.
Let's change our other matchers as well.
Now, although this works, it gets a bit unwieldy.
Instead of writing out the matchers each time
that we need them, let's extract them into variables
that we can reuse throughout our tests.
That's starting to look nice, but we still
haven't verified anything.
Let's look for the node that has a text 1 plus 2 plus 3.
We can call different assert methods on it.
In our case, let's verify that this node indeed exists.
Congratulations.
We made our first test.
If, for whatever reason, our formula field
doesn't work as expected, this test will fail,
and you can investigate the issue.
Now, we tested the happy path.
But let's try to do an invalid action
and verify that our app handles it gracefully.
We will enter a percentage sine into a formula field
and press the equals sign to try to perform that calculation.
How will our app react?
Wow.
It shows a nice error message to tell the user
that there is a format error.
Let's validate that this format error
is shown by finding a node with the text format error
and asserting it exists.
Then, when pressing the backspace to undo our mistake,
we can validate that the error is correctly removed
and the node doesn't exist anymore.
Now, in this test, we don't really
care about the actual string that we're showing.
We could change it to validation error, or just error.
And our tests should still work.
So instead of matching on a hardcoded string,
we should match on the string resource
from our strings.xml file instead.
But accessing string resources means that we need access
to an activity.
And that's not the default behavior for our Compose tests.
To access the activity, we need to change our Compose rule
to an AndroidComposeRule that references an activity
to start before the test runs.
ComponentActivity is an empty activity
that serves as a host for our Compose content.
Now we have a working test that keeps working
when the string changes.
Looking at this test, there's another property that
would be interesting to test.
What if we want to verify that the error code is
shown in this orange color?
There's actually no method to assert the color of our text.
To understand why, we have to understand a bit more
about the concept of nodes in Compose.
The nodes we match in our tests are so-called semantic nodes.
And they're part of the semantics tree.
Parallel to the UI tree that contains visual information
of your app, the semantics tree is
an alternative representation of the information
on your screen, where each node describes the semantic meaning
of a screen element.
It contains properties, such as text and content
description, but also actions, such as onClick and setText.
It also has a reference to its parent and to its children.
The matchers in our tests use these properties
to test the semantics of our UI.
That's why we also call these tests Semantic UI tests.
In comparison, you can use so-called screenshot tests
to verify visual properties, but that's
a topic for another talk.
And with this last bit of theory,
I hope you learned something about UI testing in Compose.
We barely scratched the surface, so here
are some other resources on the subject.
Now, start adding tests and subtracting bugs.
[MUSIC PLAYING]