openrewrite / rewrite-testing-frameworks

OpenRewrite recipes that perform common Java testing migration tasks.
Apache License 2.0
77 stars 73 forks source link

Hamcrest to JUnit Jupiter migration recipes #212

Open yeikel opened 2 years ago

yeikel commented 2 years ago

While the Hamcrest repo is still active, the latest release is from Oct 16, 2019

I think that this is a potential good use case for a migration recipe

We could have two different types of migration recipes (and issues)

  1. https://github.com/openrewrite/rewrite-testing-frameworks/issues/212
  2. https://github.com/openrewrite/rewrite-testing-frameworks/issues/357

Relevant discussion : https://github.com/hamcrest/JavaHamcrest/issues/353

tkvangorder commented 2 years ago

For context, we had outlined some specifics around this migration. We ended up closing the issue because we did not have the resources to work on it at the time.

https://github.com/openrewrite/rewrite-testing-frameworks/issues/16

rovarga commented 1 year ago

One thing that might make sense is the straightforward migration of assertThat(foo, instanceOf(Foo.class)) to JUnit5 assertInstanceOf(Foo.class, foo). That would provide a framework-independent migration, potentially lowering dependencies on Hamcrest for JUnit5 users.

timtebeek commented 1 year ago

@rovarga That would indeed make sense as a starting point; is that something you'd be willing to contribute with some guidance? I suspect there's quite a few more in #16 that are easy to tackle once an initial sample recipe is available.

rovarga commented 1 year ago

@rovarga That would indeed make sense as a starting point; is that something you'd be willing to contribute with some guidance? I suspect there's quite a few more in #16 that are easy to tackle once an initial sample recipe is available.

Yes I would certainly be interested, unfortunately I do not have the cycles right now. Perhaps in a couple of weeks (I will keep this on my radar).

timtebeek commented 1 year ago

Glad to hear you're interested in helping out! Let us know how we can best support you. I'll pitch a quick outline right now.

Looking through the list in #16 I'm seeing quite a few matchers that have one to one replacements in AssertJ, which might be the easiest to tackle. Assuming here that people picked Hamcrest for the fluent matchers, and then AssertJ is the most direct replacement to that.

Alternatively we can (only) migrate to JUnit 5, and then optionally after that chain the existing migration from JUnit 5 to AssertJ; that way we can have both, provided there's equivalents for all matchers in both JUnit 5 and AssertJ, and recipes to go between those.

Either way I suppose you can start by creating a recipe that takes in two options:

  1. a String method pattern of a Matchers method to replace, for instance org.hamcrest.Matchers equalTo.
  2. the replacement method pattern; so for AssertJ that would be org.assertj.core.api.AbstractAssert isEqualTo

The recipe can then match on the first method pattern, and replace the surrounding org.hamcrest.MatcherAssert.assertThat with a call to org.assertj.core.api.Assertions.assertThat with the same first argument, and using the hamcrest matcher argument in a chained call to the replacement method.

-org.hamcrest.MatcherAssert.assertThat(actual, equalTo(expected));
+org.assertj.core.api.Assertions.assertThat(actual).isEqualTo(expected);

or alternatively for JUnit 5

-org.hamcrest.MatcherAssert.assertThat(actual, equalTo(expected));
+org.junit.jupiter.api.Assertions.assertEquals(expected, actual);

Of course with appropriate new imports, removing the old imports, and using JavaTemplate to select the correct overloaded method where applicable for AssertJ. There should be plenty of examples to see how to achieve that, even if it can be daunting at first.

With the above you'd then be able to convert quite a few similar Hamcrest matchers using a single recipe, by configuring the options in a yaml recipe file. That way you'll have maximum output from a single Java recipe.

Anything the generic recipe can not handle, can then be picked up separately in a dedicated recipe for that use case. We prefer to maintain multiple relatively simple recipes, rather than one recipe to migrate all use cases.

Hope this helps!