[英]unit test a interface implementation with mock in Spring Boot
我正在嘗試為 Spring Boot 中的服務編寫一個簡單的單元測試。
該服務調用存儲庫上的方法,該方法返回用戶的實例。 我正在嘗試模擬存儲庫,因為我只想測試服務。
因此, Repository的代碼:
public interface UserRepository extends MongoRepository<User, String> {
User findByEmail(String email);
}
服務接口:
public interface UserService {
@Async
CompletableFuture<User> findByEmail(String email) throws InterruptedException;
}
服務實施:
@Service
public class UserServiceImpl implements UserService {
private UserRepository userRepository;
// dependency injection
// don't need Autowire here
// https://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-spring-beans-and-dependency-injection.html
public UserServiceImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Async
public CompletableFuture<User> findByEmail(String email) throws InterruptedException {
User user = userRepository.findByEmail(email);
return CompletableFuture.completedFuture(user);
}
}
單元測試:
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTest {
@InjectMocks
UserService userService;
@Mock
UserRepository mockUserRepository;
@Before
public void setUp() {
MockitoAnnotations.initMock(this);
}
@Test
public void mustReturnUser() throws InterruptedException {
String emailTest = "foo@bar.com";
User fakeUser = new User();
fakeUser.setEmail(emailTest);
when(mockUserRepository.findByEmail(emailTest)).thenReturn(fakeUser);
User user = userService.findByEmail(emailTest).join();
assertThat(user).isEqualTo(fakeUser);
verify(mockUserRepository).findByEmail(emailTest);
}
}
當我運行這個測試時,我得到了一個MockitoException
:
org.mockito.exceptions.base.MockitoException:
Cannot instantiate @InjectMocks field named 'userService'.
...
Caused by: org.mockito.exceptions.base.MockitoException: the type 'UserService' is an interface.
我沒有使用接口,而是嘗試使用真正的實現; 像這樣改變測試:
@InjectMocks
UserServiceImpl userService;
現在,測試成功通過,但這似乎不正確(至少對我而言)。 我喜歡測試 Spring Boot 正在使用的 UserService(假設在我的系統的新版本中,我實現了一個新的 UserServicePostgreSQLImpl - 現在我正在使用 MongoDB)。 (編輯:請參閱問題中的底部編輯)
我改變了單元測試如下:
@Autowired
@InjectMocks
UserService userService;
但現在我測試失敗了:
Expected :model.User@383caf89
Actual :null
出於某種原因,當我使用@Autowired
, UserRepository
模擬不起作用。 如果我將emailTest
更改為在我的數據庫中使用真實的電子郵件,則測試通過。 當我使用@Autowired
,測試使用真實UserRepository
而不是Mock
版本的UserRepository
。
有什么幫助嗎?
編輯:查看@msfoster 和@dunni 的答案,並更好地思考,我相信正確的方法是測試每個實現(在我的示例中,使用UserServiceImpl userService
)。
為了在使用 @InjectMocks 注釋時自動裝配 UserServiceImpl,它需要注冊為 Spring bean 本身。 您可以通過使用 @Service 注釋 UserServiceImpl 類來最簡單地做到這一點。
這將確保它被 Spring boot 配置中的組件掃描選中。 (只要掃描包含您的服務類所在的包!)
您正在使用SpringRunner
運行測試,但對於SpringRunner
,您實際上並不需要 spring 上下文。 試試下面的代碼
// Using mockito runner
@RunWith(MockitoJUnitRunner.class)
public class UserServiceTest {
@Mock
UserRepository mockUserRepository;
// Mockito will auto inject mockUserRepository mock to userService via constructor injection
@InjectMocks
UserService userService;
@Test
public void mustReturnUser() throws InterruptedException {
String emailTest = "foo@bar.com";
User fakeUser = new User();
fakeUser.setEmail(emailTest);
when(mockUserRepository.findByEmail(emailTest)).thenReturn(fakeUser);
User user = userService.findByEmail(emailTest).join();
assertThat(user).isEqualTo(fakeUser);
verify(mockUserRepository).findByEmail(emailTest);
}
}
這只是@Yogesh Badke 答案的一個變體。
雖然你在運行時使用的是spring,但是單元測試時沒有必要使用spring。 相反,您可以在測試設置期間模擬所有依賴項並將它們設置為模擬(使用反射或設置器,如果有的話)。
下面是一些示例代碼:
import org.springframework.test.util.ReflectionTestUtils;
public class TestUserService
{
private static final String VALUE_EMAIL = "test email value";
private UserService classToTest;
@Mock
private User mockUser;
@Mock
private UserRepository mockUserRepository;
@Before
public void beforeTest()
{
MockitoAnnotations.initMock(this);
classToTest = new UserService();
doReturn(mockUser).when(mockUserRepository).findByEmail(VALUE_EMAIL);
ReflectionTestUtils.setField(
classToTest,
"userRepository",
mockUserRepository);
}
@Test
public void findByEmail_goodEmailInput_returnsCorrectUser()
{
final User actualResult;
actualResult = classToTest.findByEmail(VALUE_EMAIL);
assertSame(
mockUser,
actualResult);
}
}
如果接口由多個類實現,則使用 Junit beans.xml 文件中的限定符名稱(下面的示例)來運行相應的 Junit 測試用例。
示例:
@Autowired
@Qualifier("animal")
private Animal animals;
在 Junit beans.xml 中
<bean id="animal" class="com.example.abc.Lion"/>
其中 Lion 是 Interface Animal 的實現類。
您需要為實現類使用@InjectMocks
。 不是接口類。
示例:
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTest {
@Mock
UserRepository mockUserRepository;
@InjectMocks
UserServiceImpl userServiceImpl; ------> This is important
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.