在Spring Boot中使用mock测试接口实现

时间:2017-08-03 13:41:28

标签: java spring unit-testing

我正在尝试为Spring Boot中的服务编写一个简单的单元测试。

该服务调用存储库上的方法,该存储库返回User的实例。 我正在尝试模拟存储库,因为我只想测试服务。

所以,存储库的代码:

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)。

5 个答案:

答案 0 :(得分:3)

为了让UserServiceImpl在使用@InjectMocks注释时自动装配,那么它需要注册为Spring bean本身。您可以通过使用@Service注释UserServiceImpl类来最简单地完成此任务。

这将确保在Spring启动配置中通过组件扫描获取它。 (只要扫描包含您的服务类所在的包!)

答案 1 :(得分:1)

您正在使用SpringRunner运行测试,但对于模拟,您并不需要弹簧上下文。请尝试以下代码

// 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);
  }
}

答案 2 :(得分:0)

这只是@Yogesh Badke答案的变体。

虽然你在运行时使用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);
    }
}

答案 3 :(得分:0)

如果接口由多个类实现,则使用Junit beans.xml文件中的限定符名称(下面的示例)来运行相应的Junit测试用例。

示例:

 @Autowired
    @Qualifier("animal")
    private Animal animals;

在Junit beans.xml中

  <bean id="animal" class="com.example.abc.Lion"/>

其中Lion是Interface Animal的实现类。

答案 4 :(得分:0)

您需要为实现类@InjectMocks。不是接口类。

示例:

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTest {

  @Mock
  UserRepository mockUserRepository;

  @InjectMocks
  UserServiceImpl userServiceImpl; ------> This is important
}