Enforcing Team Rules with Lint: Tests 🧐
Tags: androidstudio, lint
Published: 2021-09-14 01:31:54

---
title: Enforcing Team Rules with Lint: Tests 🧐
published: true
date: 2020-11-20 00:00:00 UTC
tags: androidstudio,lint
canonical_url: https://zarah.dev/2020/11/20/todo-test.html
---

A few months ago, my team came upon an agreement that when leaving a TODO anywhere in our code, we need to always provide several things:

- the person who is expected to address the TODO
- date when the TODO was left
- a comment or explanation on what needs to be done

I created a [live template](https://dev.to/zmdominguez/todo-live-templates-5199-temp-slug-8462254) to support adherence to this rule, but why not go one step further and integrate the rule into our daily workflow?

We have previously seen:

- [the foundations we have started](https://dev.to/zmdominguez/enforcing-team-rules-with-lint-5fej-temp-slug-8783794)
- [what detectors are and how to write them](https://dev.to/zmdominguez/enforcing-team-rules-with-lint-detectors-6af-temp-slug-2689931)

* * *

Having tests for our custom Lint rule is really important. We do not want Lint to flag errors, uhm, erroneously. It results in frustration and users might just turn off our rule.

### Serving up files 💁

Lint checks run on files, so for each of our test cases we need to provide mock files.

Lint provides a [`TestFile`](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-tests/src/main/java/com/android/tools/lint/checks/infrastructure/TestFile.java) API that allows us to create these mock files inline.

```
TestFiles.java(
    """
        package test.pkg;
        public class TestClass1 {
            // In a comment, mentioning "lint" has no effect
         }
    """
)


TestFiles.kotlin(
     """
        package test.pkg
        class TestClass {
            // In a comment, mentioning "lint" has no effect
        }
    """

```

I am using [raw strings](https://kotlinlang.org/docs/reference/basic-types.html#string-literals) so that I do not have to worry about escaping special characters.

It also gives us very nice syntax highlighting!

<center>
    <a href="https://imgur.com/zkA28LD"><img src="https://i.imgur.com/zkA28LD.png" title="source: imgur.com"></a><br>
</center>

Furthermore, if you choose “Edit Kotlin Fragment” from the hint, Android Studio will open up a file editor. Any changes you make in this editor will immediately reflect in your `TestFile`. Pretty cool!

<center>
    <a href="https://imgur.com/PQmaNBP"><img src="https://i.imgur.com/PQmaNBP.png" title="source: imgur.com"></a><br>
</center>
### Let’s get testing 🔬

The gateway to our test cases is [TestLintTask](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-tests/src/main/java/com/android/tools/lint/checks/infrastructure/TestLintTask.java). We need to provide it the following:

- files we want to run our checks on
- the issue we are testing against
- expected result

```
@Test
fun testKotlinFileNormalComment() {
    TestLintTask.lint()
        .files(
            TestFiles.kotlin(
                """
                    package test.pkg
                        
                    class TestClass {
                        // In a comment, mentioning "lint" has no effect
                    }
                """
            )
        )
        .issues(TodoDetector.ISSUE)
        .run()
        .expect("No warnings.")
}

```

Note that we have the option here of using `expect("No warnings.")` or `expectClean()`.

For the test cases where we expect an error to occur, we need to put in the text that Lint spits out (i.e. similar to what you see in the console when running `.gradlew :app:lintDebug`). The trickiest thing about this is that the string has to match _exactly_, including where the squiggly lines are.

The easiest way to do this is to pass an empty string to `expect()` and let the test fail. You can then copy-paste the error message into your test.

<center>
    <a href="https://imgur.com/TMaZBuX"><img src="https://i.imgur.com/TMaZBuX.png" title="source: imgur.com"></a><small>Retrieving the message for an error scenario</small><br>
</center>

I wrote a few tests for the detector covering Java and Kotlin files, incorrect date formats, and “TODO” casing. You can find them all [here](https://github.com/zmdominguez/sdk_sandbox/blob/main/checks/src/test/java/dev/zarah/lint/checks/TodoDetectorTest.kt).

### Bringing it all together 🤝

Now that we have written our tests, it’s finally time to integrate our Lint rule into our app!

First we need to create our [`IssueRegistry`](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/IssueRegistry.kt) to let Lint know about our custom rule. We also need to provide an API value; for custom rules we can use the constant defined by the Lint API.

```
@Suppress("UnstableApiUsage")
class IssueRegistry : IssueRegistry() {
    override val issues: List<Issue> = listOf(
        TodoDetector.ISSUE
    )

    override val api = CURRENT_API
}

```

We then register our `IssueRegistry` by creating a file with the fully qualified name of our file under a `META-INF/services/` folder:

```
src/main/resources/META-INF/services/dev.zarah.lint.checks.IssueRegistry

```

:raising\_hand: Most posts mention that adding this registry is enough, and I am probably doing something wrong but I found out that for my project, I still have to include my `IssueRegistry` in the Manifest by adding this to my `build.gradle.kts` file:

```
tasks {
  jar {
    manifest {
      attributes(
          "Lint-Registry-v2" to "dev.zarah.lint.checks.IssueRegistry"
      )
    }
  }
}

```

We have set up everything and now it’s time to consume our custom lint rule! In the modules where you want the rules applied, add a `lintChecks` entry in the `dependencies` closure, build your project, and everything should be good to go! :running\_woman:

```
dependencies {
    lintChecks project(':checks')
}

```

### Seeing it in action 📽️

Finally! We have come to the end of our journey! Here’s our detector in action:

<center>
    <a href="https://imgur.com/oUivO3A"><img src="https://i.imgur.com/oUivO3A.gif" title="source: imgur.com"></a><small>Custom lint rule with quickfix</small><br>
</center>

The source code for this Lint series is [available on Github](https://github.com/zmdominguez/sdk_sandbox/tree/main/checks) ([diff](https://github.com/zmdominguez/sdk_sandbox/pull/25)).

* * *

:bowing\_woman: I have learned _so many_ things whilst I was doing this task.

As I [mentioned on Twitter](https://twitter.com/zarahjutz/status/1329398431924699137), there’s barely any public documentation at all. The talks I’ve seen are way too advanced for me (for example, I _needed_ to understand what PSI and UAST were before it clicked and most talks barely even define them).

There was a lot of trial and error, and so. much. guesswork. It was incredibly frustrating. Major props to [Mike Evans](https://twitter.com/m_evans10) who patiently guided me through the hours and hours of pair programming that we did. If he didn’t help, I wouldn’t even know where to start. Sure I can copy-paste from samples but I want to understand _why_ things are done the way they are – that’s how I learn.

I probably have made wrong assumptions whilst writing these posts :woman\_shrugging: but again, no documentation so this is the best I could do. :sweat\_smile:

Anyway, what I want to say is, I usually only see the final output of other people’s work and sometimes I cannot help but feel jealous – how come they find it super easy whilst I’m out here crying because I don’t understand anything? I needed to remind myself that it’s okay to be frustrated, it’s okay to take your time to learn, and it’s okay to ask for help.

### Further reading (and/or watching) 📖

If you’re keen to learn more, here are some resources about Lint and custom Lint rules:

- [Coding in Style: Static Analysis with Custom Lint Rules, Android Dev Summit 2019](https://youtu.be/jCmJWOkjbM0) (Alan Viverette, Rahul Ravikumar)
- [Kotlin Static Analysis with Android Lint](https://youtu.be/p8yX5-lPS6o) (Tor Norbye)
- [Lint API source code](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/)
- [AndroidX documentation](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:docs/LINT.md)
- [Android Lint Checks demo](https://github.com/alexjlockwood/android-lint-checks-demo) (Alex Lockwood)
- [lint-dev Google group](https://groups.google.com/g/lint-dev)