Replace method with JavaParser library
Tags: java, javaparser
Published: 2020-10-31 07:57:27

# Motivation

Recently I want to migrate from JUnit `assertEquals` to Hamcrest `assertThat` and `is` to improve readability of my unit tests.

Instead of using regular expression, it seems it is safer to parse the syntax and manipulate it, so I choose [JavaParser](http://javaparser.org/).

# Objective

Here is how existing test looks like.

```java
package com.franzwong.playground;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

class MyTest {
    @Test
    public void test() {
        String a = "123";
        assertEquals("123", a);
    }
}
```

I would like to change it to something like this.

```java
package com.franzwong.playground;

import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.CoreMatchers.is;

class MyTest {
    @Test
    public void test() {
        String a = "123";
        assertThat(a, is("123"));
    }
}

```

Action items:

1. Remove the static import of `assertEquals`
2. Add static import for `assertThat` and `is`
3. Convert `assertEquals("123", a)` to `assertThat(a, is("123"))`
4. Save modified source

# Solution

## Add Maven dependency for JavaParser

```xml
<dependency>
    <groupId>com.github.javaparser</groupId>
    <artifactId>javaparser-core</artifactId>
    <version>3.16.2</version>
</dependency>
```

## Setup `CompilationUnit`

Suppose `MyTest.java` file is stored in `/tmp` folder.

```java
SourceRoot sourceRoot = new SourceRoot(Path.of("/tmp"));
CompilationUnit cu = sourceRoot.parse("", "MyTest.java");
```

## Remove the static import of `assertEquals`

We just need to walk through the syntax tree. If the import declaration node is found and it is the `assertEquals` node, we just remove it.

```java
cu.walk(ImportDeclaration.class, e -> {
    if (e.isStatic() && "org.junit.jupiter.api.Assertions.assertEquals".equals(e.getNameAsString())) {
        e.remove();
    }
});
```

## Add static import for `assertThat` and `is`

Parameters of ImportDeclaration constructor
* 1st parameter is the static method name
* 2nd parameter is true if it is a static import
* 3rd parameter is true if it is asterisk

```java
cu.addImport(new ImportDeclaration("org.hamcrest.MatcherAssert.assertThat", true, false));
cu.addImport(new ImportDeclaration("org.hamcrest.CoreMatchers.is", true, false));
```

## Convert `assertEquals("123", a)` to `assertThat(a, is("123"))`

We walk through the syntax tree again. If it is a `MethodCallExpr` node and its name is `assertEquals`, we rename it and change its parameters.

```java
cu.walk(MethodCallExpr.class, e -> {
  if (!"assertEquals".equals(e.getNameAsString())) {
      return;
  }

  // Rename method
  e.setName("assertThat");

  // Get the parameter list of `assertEquals`
  NodeList<Expression> equalsArgments = e.getArguments();

  // Create a new `is` method and assign 1st parameter of `assertEquals` to it
  MethodCallExpr isMethod = new MethodCallExpr("is", equalsArgments.get(0));

  // Change the parameters
  equalsArgments.set(0, equalsArgments.get(1));
  equalsArgments.set(1, isMethod);
});
```

## Save modified source

We just need to provide a folder path to it.

```java
sourceRoot.saveAll(Path.of("/tmp/output"));
```

So that's it. Here is how the whole source code looks like. I hope this tutorial is useful to you.

```java
public static void main(String[] args) {

  SourceRoot sourceRoot = new SourceRoot(Path.of("/tmp"));
  CompilationUnit cu = sourceRoot.parse("", "MyTest.java");

  cu.walk(ImportDeclaration.class, e -> {
    if (e.isStatic() && "org.junit.jupiter.api.Assertions.assertEquals".equals(e.getNameAsString())) {
      e.remove();
    }
  });

    cu.addImport(new ImportDeclaration("org.hamcrest.MatcherAssert.assertThat", true, false));
    cu.addImport(new ImportDeclaration("org.hamcrest.CoreMatchers.is", true, false));

  cu.walk(MethodCallExpr.class, e -> {
    if (!"assertEquals".equals(e.getNameAsString())) {
      return;
    }

    // Rename method
    e.setName("assertThat");

    // Get the parameter list of `assertEquals`
    NodeList<Expression> equalsArgments = e.getArguments();

    // Create a new `is` method and assign 1st parameter of `assertEquals` to it
    MethodCallExpr isMethod = new MethodCallExpr("is", equalsArgments.get(0));

    // Change the parameters
    equalsArgments.set(0, equalsArgments.get(1));
    equalsArgments.set(1, isMethod);
  });

  sourceRoot.saveAll(Path.of("/tmp/output"));
}
```