Skip to content

etrandafir93/utilitest

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Utilitest

Utilitest is a collection of test utilities that provides integration support for popular testing libraries like AssertJ, Mockito, and Awaitility.

Go ahead and add the latest version of the library to your project:

<dependency>
  <groupId>io.github.etrandafir93</groupId>
  <artifactId>utilitest</artifactId>
  <version>${version}</version>
  <scope>test</scope>
</dependency>

Mockito's AssertMatcher and AsserJ

To use verify() mocks, we generally have two options. We can either capture the arguments using a Captor, which adds some boilerplate code and overhead, or we can use Mockito's ArgumentMatchers to verify the arguments directly with a lambda:

@Test
void customAssertMatcher() {
  FooService mock = Mockito.mock();
  mock.process(new Account(1L, "John Doe", "johnDoe@gmail.com"));

  Mockito.verify(mock).process(
    Mockito.argThat(
      it -> it.getAccountId().equals(1L)
        && it.getName().equals("John Doe")
        && it.getEmail().equals("johndoe@gmail.com")));
}

However, the failure messages from these custom argument matchers are often cryptic, making it difficult to pinpoint the cause of the test failure:

Argument(s) are different! Wanted:
fooService.process(
    <custom argument matcher>
);
-> at io.github.etr.utilitest.ReadmeExampelsTest$FooService.process(ReadmeExampelsTest.java:26)
Actual invocations have different arguments:
fooService.process(
    io.github.etr.utilitest.ReadmeExampelsTest$Account@245a26e1
);
-> at io.github.etr.utilitest.ReadmeExampelsTest.customAssertMatcher(ReadmeExampelsTest.java:62)

As a workaround, we can use a fluent AssertJ assertion within the custom ArgumentMatcher and always return true:

@Test
void assertMatcherWithAssertJ() {
    FooService mock = Mockito.mock();
    mock.process(new Account(1L, "John Doe", "johnDoe@gmail.com"));

    Mockito.verify(mock).process(
      Mockito.argThat(it -> {
        Assertions.assertThat(it)
          .hasFieldOrPropertyWithValue("accountId", 1L)
          .hasFieldOrPropertyWithValue("name", "John Doe")
          .hasFieldOrPropertyWithValue("email", "johndoe@gmail.com");
        return true;
      }));
}

While this solution provides much clearer error messages, it comes with the downside of adding a lot of boilerplate code. If the code is repetitive, it can make the tests harder to read and maintain.

So, let's remove all this ceremony and use utilitest's MockitoAndAssertJ::argThat instead:

Mockito.verify(mock).process(
  MockitoAndAssertJ.argThat(it -> it
    .hasFieldOrPropertyWithValue("accountId", 1L)
    .hasFieldOrPropertyWithValue("name", "John Doe")
    .hasFieldOrPropertyWithValue("email", "johndoe@gmail.com")));

This approach brings together the best of both worlds: the convenience of verifying the argument using a lambda expression and the fluent API of AssertJ, providing clear and descriptive error messages:

java.lang.AssertionError: 
Expecting
  io.github.etr.utilitest.ReadmeExampelsTest$Account@f5c79a6
to have a property or a field named "email" with value
  "johndoe@gmail.com"
but value was:
  "johnDoe@gmail.com"

Other Assertions

The MockitoAndAssertJ::argThat from the previous example enables us to consume an ObjectAssert from AsserJ, that provides some basic assertions. The assertJ API allows us to change this type to a more specialized instance of assertion, to verify specific properties. For example, we can change the assertion type to a MapAssert to be able to check speciifc key-value entries:

@Test
void asInstanceOf() {
  Map<Long, String> data = Map.of(
    1L, "John",
    2L, "Bobby"
  );

  FooService mock = Mockito.mock();
  mock.processMap(data);

  verify(mock).processMap(
    MockitoAndAssertJ.argThat(it -> it
      .asInstanceOf(InstanceOfAssertFactories.MAP)
      .containsEntry(1L, "John")
      .containsEntry(2L, "Bobby")));
}

With MockitoAndAssertJ, we can also specify InstanceOfAssertFactories upfront. To achieve this, we split the argThat into two separate methods: MockitoAndAssertJ.arg(InstanceOfAssertFactories.MAP).that(it -> ...).

Let's use this API to verify a method that accepts a LocalDateTime and a List of Strings:

@Test
void arg_that() {
  FooService mock = Mockito.mock();
  mock.processDateAndList(now(), List.of("A", "B", "C"));

  verify(mock).processDateAndList(
     arg(TEMPORAL).that(time -> time.isCloseTo(now(), within(500, MILLIS))),
     arg(LIST).that(list -> list.containsExactly("A", "B", "C"))
  );
}

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Languages