Quantcast
Channel: Baeldung
Viewing all articles
Browse latest Browse all 3522

Mockito and JUnit 5 – Using ExtendWith

$
0
0

1. Introduction

In this quick article, we’ll show how to integrate Mockito with the JUnit 5 extension model. To learn more about the JUnit 5 extension model, have a look at this article.

First, we’ll show how to create an extension that automatically creates mock objects for any class attribute or method parameter annotated with @Mock.

Then, we’ll use our Mockito extension in a JUnit 5 test class.

2. Maven Dependencies

2.1. Required Dependencies

Let’s add the JUnit 5 (jupiter) and mockito dependencies to our pom.xml:

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <version>5.0.1</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>2.11.0</version>
    <scope>test</scope>
</dependency>

Note that junit-jupiter-engine is the main JUnit 5 library, and junit-platform-launcher is used with the Maven plugin and IDE launcher.

2.2. Surefire Plugin

Let’s also configure the Maven Surefire plugin to run our test classes using the new JUnit platform launcher:

<plugin>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.19.1</version>
    <configuration>
        <includes>
            <include>**/*Test.java</include>
        </includes>
    </configuration>
    <dependencies>
        <dependency>
            <groupId>org.junit.platform</groupId>
            <artifactId>junit-platform-surefire-provider</artifactId>
            <version>1.0.1</version>
        </dependency>
    </dependencies>
</plugin>

2.3. JUnit 4 IDE Compatibility Dependencies

For our test cases to be JUnit4 (vintage) compatible, for IDEs that have no support for JUnit 5 yet, let’s include these dependencies:

<dependency>
    <groupId>org.junit.platform</groupId>
    <artifactId>junit-platform-launcher</artifactId>
    <version>1.0.1</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.junit.vintage</groupId>
    <artifactId>junit-vintage-engine</artifactId>
    <version>4.12.1</version>
    <scope>test</scope>
</dependency>

Also, we should consider annotating all our test classes with @RunWith(JUnitPlatform.class)

The latest versions of junit-jupiter-engine, junit-vintage-enginejunit-platform-launcher, and mockito-core can be downloaded from Maven Central.

3. Mockito Extension

Until now, there is no official JUnit5 extension implementation from Mockito, so we’ll be using the default implementation provided by the JUnit team.

This extension implements the TestInstancePostProcessor interface for creating mock objects for class attributes and the ParameterResolver interface for creating mock objects for method parameters:

public class MockitoExtension 
  implements TestInstancePostProcessor, ParameterResolver {

    @Override
    public void postProcessTestInstance(Object testInstance,
      ExtensionContext context) {
        MockitoAnnotations.initMocks(testInstance);
    }

    @Override
    public boolean supportsParameter(ParameterContext parameterContext,
      ExtensionContext extensionContext) {
        return 
          parameterContext.getParameter().isAnnotationPresent(Mock.class);
    }

    @Override
    public Object resolveParameter(ParameterContext parameterContext,
      ExtensionContext extensionContext) {
        return getMock(parameterContext.getParameter(), extensionContext);
    }

    private Object getMock(
      Parameter parameter, ExtensionContext extensionContext) {
        
        Class<?> mockType = parameter.getType();
        Store mocks = extensionContext.getStore(Namespace.create(
          MockitoExtension.class, mockType));
        String mockName = getMockName(parameter);

        if (mockName != null) {
            return mocks.getOrComputeIfAbsent(
              mockName, key -> mock(mockType, mockName));
        }
        else {
            return mocks.getOrComputeIfAbsent(
              mockType.getCanonicalName(), key -> mock(mockType));
        }
    }

    private String getMockName(Parameter parameter) {
        String explicitMockName = parameter.getAnnotation(Mock.class)
          .name().trim();
        if (!explicitMockName.isEmpty()) {
            return explicitMockName;
        }
        else if (parameter.isNamePresent()) {
            return parameter.getName();
        }
        return null;
    }
}

Let’s review the methods implemented in this extension:

  • postProcessTestInstance — initializes mock objects for all attributes of testInstance object
  • supportsParameter — tells JUnit that our extension may handle this method parameter if it is annotated with @Mock
  • resolveParameter — initializes the mock object for the given parameter

4. Building the Test Class

Let’s build our test class and attach the Mockito extension to it:

@ExtendWith(MockitoExtension.class)
@RunWith(JUnitPlatform.class)
public class UserServiceTest {
    UserService userService;

    //...
}

We can use the @Mock annotation to inject a mock for an instance variable that we can use anywhere in the test class:

@Mock UserRepository userRepository;

Also, we can inject mock objects into method parameters:

@BeforeEach
void init(@Mock SettingRepository settingRepository,
  @Mock MailClient mailClient) {
    userService = new DefaultUserService(
      userRepository, settingRepository, mailClient);
    
    when(settingRepository.getUserMinAge()).thenReturn(10);
    when(settingRepository.getUserNameMinLength()).thenReturn(4);
    when(userRepository.isUsernameAlreadyExists(any(String.class)))
      .thenReturn(false);
}

We can even inject a mock object into a test method parameter:

@Test
void givenValidUser_whenSaveUser_thenSucceed(@Mock MailClient mailClient) {
    user = new User("Jerry", 12);
    when(userRepository.insert(any(User.class))).then(new Answer<User>() {
        int sequence = 1;
        
        @Override
        public User answer(InvocationOnMock invocation) throws Throwable {
            User user = (User) invocation.getArgument(0);
            user.setId(sequence++);
            return user;
        }
    });
    
    User insertedUser = userService.register(user);
    
    verify(userRepository).insert(user);
    Assertions.assertNotNull(user.getId());
    verify(mailClient).sendUserRegistrationMail(insertedUser);
}

Note that the MailClient mock that we inject as a test method parameter will be the same instance that we injected in the init method.

5. Conclusion

Junit 5 has provided a nice model for extension. We demonstrated a simple Mockito extension that simplified our mock creation logic.

All the code used in this article can be found in the com.baeldung.junit5.mockito package of the GitHub project, along with a few additional unit test methods.


Viewing all articles
Browse latest Browse all 3522

Trending Articles