From 26f38c1b69f29e92ecef3f2e8988b5a772256a00 Mon Sep 17 00:00:00 2001 From: Hany Ahmed Date: Sat, 28 Oct 2017 04:48:33 +0200 Subject: [PATCH] JUnit5 with Mockito using @ExtendWith (#2770) * JUnit5 with Mockito using @ExtendWith * rename method: givenUserWithExistingName_whenSaveUser_thenGiveUsernameAlreadyExistsError rename this class according to the standard -- UserServiceUnitTest.java --- junit5/pom.xml | 7 +- .../com/baeldung/junit5/mockito/User.java | 41 ++++++ .../junit5/mockito/repository/MailClient.java | 9 ++ .../mockito/repository/SettingRepository.java | 9 ++ .../mockito/repository/UserRepository.java | 10 ++ .../mockito/service/DefaultUserService.java | 46 +++++++ .../junit5/mockito/service/Errors.java | 10 ++ .../junit5/mockito/service/UserService.java | 9 ++ .../junit5/mockito/MockitoExtension.java | 75 +++++++++++ .../junit5/mockito/UserServiceUnitTest.java | 122 ++++++++++++++++++ 10 files changed, 335 insertions(+), 3 deletions(-) create mode 100644 junit5/src/main/java/com/baeldung/junit5/mockito/User.java create mode 100644 junit5/src/main/java/com/baeldung/junit5/mockito/repository/MailClient.java create mode 100644 junit5/src/main/java/com/baeldung/junit5/mockito/repository/SettingRepository.java create mode 100644 junit5/src/main/java/com/baeldung/junit5/mockito/repository/UserRepository.java create mode 100644 junit5/src/main/java/com/baeldung/junit5/mockito/service/DefaultUserService.java create mode 100644 junit5/src/main/java/com/baeldung/junit5/mockito/service/Errors.java create mode 100644 junit5/src/main/java/com/baeldung/junit5/mockito/service/UserService.java create mode 100644 junit5/src/test/java/com/baeldung/junit5/mockito/MockitoExtension.java create mode 100644 junit5/src/test/java/com/baeldung/junit5/mockito/UserServiceUnitTest.java diff --git a/junit5/pom.xml b/junit5/pom.xml index 1fa4818447..b820d7b7bc 100644 --- a/junit5/pom.xml +++ b/junit5/pom.xml @@ -19,11 +19,12 @@ UTF-8 1.8 - 5.0.0-RC2 - 1.0.0-RC2 - 4.12.0-RC2 + 5.0.1 + 1.0.1 + 4.12.1 2.8.2 1.4.196 + 2.11.0 3.6.0 2.19.1 diff --git a/junit5/src/main/java/com/baeldung/junit5/mockito/User.java b/junit5/src/main/java/com/baeldung/junit5/mockito/User.java new file mode 100644 index 0000000000..4276fd62b9 --- /dev/null +++ b/junit5/src/main/java/com/baeldung/junit5/mockito/User.java @@ -0,0 +1,41 @@ +package com.baeldung.junit5.mockito; + +public class User { + + private Integer id; + private String name; + private int age; + + public User() { + } + + public User(String name, int age) { + this.name = name; + this.age = age; + } + + public Integer getId() { + return id; + } + public void setId(Integer id) { + this.id = id; + } + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public int getAge() { + return age; + } + public void setAge(int age) { + this.age = age; + } + + @Override + public String toString() { + return "User [id=" + id + ", name=" + name + ", age=" + age + "]"; + } + +} diff --git a/junit5/src/main/java/com/baeldung/junit5/mockito/repository/MailClient.java b/junit5/src/main/java/com/baeldung/junit5/mockito/repository/MailClient.java new file mode 100644 index 0000000000..d10169ef7a --- /dev/null +++ b/junit5/src/main/java/com/baeldung/junit5/mockito/repository/MailClient.java @@ -0,0 +1,9 @@ +package com.baeldung.junit5.mockito.repository; + +import com.baeldung.junit5.mockito.User; + +public interface MailClient { + + void sendUserRegistrationMail(User user); + +} diff --git a/junit5/src/main/java/com/baeldung/junit5/mockito/repository/SettingRepository.java b/junit5/src/main/java/com/baeldung/junit5/mockito/repository/SettingRepository.java new file mode 100644 index 0000000000..23ae8fa35c --- /dev/null +++ b/junit5/src/main/java/com/baeldung/junit5/mockito/repository/SettingRepository.java @@ -0,0 +1,9 @@ +package com.baeldung.junit5.mockito.repository; + +public interface SettingRepository { + + int getUserMinAge(); + + int getUserNameMinLength(); + +} diff --git a/junit5/src/main/java/com/baeldung/junit5/mockito/repository/UserRepository.java b/junit5/src/main/java/com/baeldung/junit5/mockito/repository/UserRepository.java new file mode 100644 index 0000000000..e1b84c774b --- /dev/null +++ b/junit5/src/main/java/com/baeldung/junit5/mockito/repository/UserRepository.java @@ -0,0 +1,10 @@ +package com.baeldung.junit5.mockito.repository; + +import com.baeldung.junit5.mockito.User; + +public interface UserRepository { + + User insert(User user); + boolean isUsernameAlreadyExists(String userName); + +} diff --git a/junit5/src/main/java/com/baeldung/junit5/mockito/service/DefaultUserService.java b/junit5/src/main/java/com/baeldung/junit5/mockito/service/DefaultUserService.java new file mode 100644 index 0000000000..888edbd710 --- /dev/null +++ b/junit5/src/main/java/com/baeldung/junit5/mockito/service/DefaultUserService.java @@ -0,0 +1,46 @@ +package com.baeldung.junit5.mockito.service; + +import com.baeldung.junit5.mockito.User; +import com.baeldung.junit5.mockito.repository.MailClient; +import com.baeldung.junit5.mockito.repository.SettingRepository; +import com.baeldung.junit5.mockito.repository.UserRepository; + +public class DefaultUserService implements UserService { + + private UserRepository userRepository; + private SettingRepository settingRepository; + private MailClient mailClient; + + public DefaultUserService(UserRepository userRepository, SettingRepository settingRepository, MailClient mailClient) { + this.userRepository = userRepository; + this.settingRepository = settingRepository; + this.mailClient = mailClient; + } + + @Override + public User register(User user) { + validate(user); + User insertedUser = userRepository.insert(user); + mailClient.sendUserRegistrationMail(insertedUser); + return insertedUser; + } + + private void validate(User user) { + if(user.getName() == null) { + throw new RuntimeException(Errors.USER_NAME_REQUIRED); + } + + if(user.getName().length() < settingRepository.getUserNameMinLength()) { + throw new RuntimeException(Errors.USER_NAME_SHORT); + } + + if(user.getAge() < settingRepository.getUserMinAge()) { + throw new RuntimeException(Errors.USER_AGE_YOUNG); + } + + if(userRepository.isUsernameAlreadyExists(user.getName())) { + throw new RuntimeException(Errors.USER_NAME_DUPLICATE); + } + } + +} diff --git a/junit5/src/main/java/com/baeldung/junit5/mockito/service/Errors.java b/junit5/src/main/java/com/baeldung/junit5/mockito/service/Errors.java new file mode 100644 index 0000000000..28283754a3 --- /dev/null +++ b/junit5/src/main/java/com/baeldung/junit5/mockito/service/Errors.java @@ -0,0 +1,10 @@ +package com.baeldung.junit5.mockito.service; + +public class Errors { + + public static final String USER_NAME_REQUIRED = "user.name.required"; + public static final String USER_NAME_SHORT = "user.name.short"; + public static final String USER_AGE_YOUNG = "user.age.young"; + public static final String USER_NAME_DUPLICATE = "user.name.duplicate"; + +} diff --git a/junit5/src/main/java/com/baeldung/junit5/mockito/service/UserService.java b/junit5/src/main/java/com/baeldung/junit5/mockito/service/UserService.java new file mode 100644 index 0000000000..326d019b4a --- /dev/null +++ b/junit5/src/main/java/com/baeldung/junit5/mockito/service/UserService.java @@ -0,0 +1,9 @@ +package com.baeldung.junit5.mockito.service; + +import com.baeldung.junit5.mockito.User; + +public interface UserService { + + User register(User user); + +} diff --git a/junit5/src/test/java/com/baeldung/junit5/mockito/MockitoExtension.java b/junit5/src/test/java/com/baeldung/junit5/mockito/MockitoExtension.java new file mode 100644 index 0000000000..5836ef46e6 --- /dev/null +++ b/junit5/src/test/java/com/baeldung/junit5/mockito/MockitoExtension.java @@ -0,0 +1,75 @@ +/* + * Copyright 2015-2016 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v1.0 which + * accompanies this distribution and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.baeldung.junit5.mockito; + +import static org.mockito.Mockito.mock; + +import java.lang.reflect.Parameter; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.junit.jupiter.api.extension.ExtensionContext.Store; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolver; +import org.junit.jupiter.api.extension.TestInstancePostProcessor; +import org.mockito.MockitoAnnotations; +import org.mockito.Mock; + +/** + * {@code MockitoExtension} showcases the {@link TestInstancePostProcessor} + * and {@link ParameterResolver} extension APIs of JUnit 5 by providing + * dependency injection support at the field level and at the method parameter + * level via Mockito 2.x's {@link Mock @Mock} annotation. + * + * @since 5.0 + */ +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; + } + +} \ No newline at end of file diff --git a/junit5/src/test/java/com/baeldung/junit5/mockito/UserServiceUnitTest.java b/junit5/src/test/java/com/baeldung/junit5/mockito/UserServiceUnitTest.java new file mode 100644 index 0000000000..1ddab0531a --- /dev/null +++ b/junit5/src/test/java/com/baeldung/junit5/mockito/UserServiceUnitTest.java @@ -0,0 +1,122 @@ +package com.baeldung.junit5.mockito; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.platform.runner.JUnitPlatform; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import com.baeldung.junit5.mockito.repository.MailClient; +import com.baeldung.junit5.mockito.repository.SettingRepository; +import com.baeldung.junit5.mockito.repository.UserRepository; +import com.baeldung.junit5.mockito.service.DefaultUserService; +import com.baeldung.junit5.mockito.service.Errors; +import com.baeldung.junit5.mockito.service.UserService; + +@RunWith(JUnitPlatform.class) +@ExtendWith(MockitoExtension.class) +public class UserServiceUnitTest { + + UserService userService; + @Mock UserRepository userRepository; + + User user; + + @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); + } + + @Test + void givenValidUser_whenSaveUser_thenSucceed(@Mock MailClient mailClient) { + // Given + user = new User("Jerry", 12); + when(userRepository.insert(any(User.class))).then(new Answer() { + int sequence = 1; + + @Override + public User answer(InvocationOnMock invocation) throws Throwable { + User user = (User) invocation.getArgument(0); + user.setId(sequence++); + return user; + } + }); + + // When + User insertedUser = userService.register(user); + + // Then + verify(userRepository).insert(user); + Assertions.assertNotNull(user.getId()); + verify(mailClient).sendUserRegistrationMail(insertedUser); + } + + @Test + void givenShortName_whenSaveUser_thenGiveShortUsernameError() { + // Given + user = new User("tom", 12); + + // When + try { + userService.register(user); + fail("Should give an error"); + } catch(Exception ex) { + assertEquals(ex.getMessage(), Errors.USER_NAME_SHORT); + } + + // Then + verify(userRepository, never()).insert(user); + } + + @Test + void givenSmallAge_whenSaveUser_thenGiveYoungUserError() { + // Given + user = new User("jerry", 3); + + // When + try { + userService.register(user); + fail("Should give an error"); + } catch(Exception ex) { + assertEquals(ex.getMessage(), Errors.USER_AGE_YOUNG); + } + + // Then + verify(userRepository, never()).insert(user); + } + + @Test + void givenUserWithExistingName_whenSaveUser_thenGiveUsernameAlreadyExistsError() { + // Given + user = new User("jerry", 12); + Mockito.reset(userRepository); + when(userRepository.isUsernameAlreadyExists(any(String.class))).thenReturn(true); + + // When + try { + userService.register(user); + fail("Should give an error"); + } catch(Exception ex) { + assertEquals(ex.getMessage(), Errors.USER_NAME_DUPLICATE); + } + + // Then + verify(userRepository, never()).insert(user); + } + +}