簡體   English   中英

在 Spring Boot 中使用模擬單元測試接口實現

[英]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

出於某種原因,當我使用@AutowiredUserRepository模擬不起作用。 如果我將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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM