我们什么时候应该使用@InjectMocks?

时间:2017-04-18 04:49:11

标签: testing junit mockito

我已经阅读了很多关于@Mock& amp; @InjectMocks仍然无法找到使用@InjectMocks的合适或必要的案例。事实上,我不确定在使用@InjectMocks时会发生什么。

考虑以下示例,

public class User {
  private String user_name;
  private int user_id;
  public User(int user_id) {
    this.user_id = user_id;
  }
  public String getUser_name() {
    return user_name;
  }
  public void setUser_name(String user_name) {
    this.user_name = user_name;
  }
  public int getUser_id() {
    return user_id;
  }
  public void setUser_id(int user_id) {
    this.user_id = user_id;
  }     
}

public interface UserDao {
  public List<User> getUserList();
}

public class UserService {
  private UserDao userDao;  
  public UserDao getUserDao() {
    return userDao;
  }
  public void setUserDao(UserDao userDao) {
    this.userDao = userDao;
  }
  public List<User> getUserList() {
    return userDao.getUserList();
  }
}

这是我的测试类

@RunWith(MockitoJUnitRunner.class)
public class UserServiceTest {  
  private UserService service = new UserService();
  @Mock
  private UserDao dao;

  @Test
  public void testGetUserList() {

    service.setUserDao(dao);

    // mock the return value of the mock object
    List<User> mockResult = Arrays.asList(new User(101),new User(102),new User(103));       
    when(dao.getUserList()).thenReturn(mockResult);

    // check return value is same as mocked value
    assertEquals(service.getUserList(),mockResult);

    // verify the getUserList() function is called
    verify(dao).getUserList();  
  }
}    

测试成功运行且没有错误。 考虑使用@InjectMock注释的另一种方法。

@RunWith(MockitoJUnitRunner.class)
public class UserServiceTest {  

  @InjectMocks
  private UserService service;
  @Mock
  private UserDao dao;

  @Test
  public void testGetUserList() {

    service.setUserDao(dao);

    // mock the return value of the mock object
    List<User> mockResult = Arrays.asList(new User(101),new User(102),new User(103));       
    when(dao.getUserList()).thenReturn(mockResult);

    // check return value is same as mocked value
    assertEquals(service.getUserList(),mockResult);

    // verify the getUserList() function is called
    verify(dao).getUserList();  
  }
}    

这同样有效。那么,哪种方式更好?任何最佳做法? 顺便说一句,我使用的是Junit 4.8.1和Mockito 1.9.5

3 个答案:

答案 0 :(得分:5)

如果没有提供更多代码,您的实际问题无法得到解答(您展示的代码并未解释您声称要遵守的结果)。

关于基本问题:你可能不应该使用@InjectMocks;其中一个核心问题是:如果注入失败,Mockito会向您报告错误。

换句话说:你已经通过了单元测试,并且有关字段的一些内部细节发生变化......并且你的单元测试中断了;但你不知道为什么......因为模拟框架并没有告诉你&#34; initial&#34;将模拟推入被测试阶段的步骤突然失败了。有关详细信息,请参阅here

但要明确:最后,这几乎是一个纯粹的风格的问题。习惯于@InjectMocks的人会喜欢它;其他人反对它。含义:没有明确的证据表明你是否应该使用这个概念。相反:你研究这个概念,你理解这个概念,然后你(以及与你一起工作的团队)有意识地决定你是否想要来使用这个注释。

编辑:我想我现在得到了你的问题。 @InjectMocks的想法是 模拟对象加入一些测试对象

但是:在这两种情况下你都是手动

 service.setUserDao(dao);

含义:如果注入正常工作(并且没有Mockito报告的问题)那么使用该注释的示例应删除那一行时工作。虽然没有@InjectMocks的测试用例失败而没有该行!

换句话说:您的测试用例都在通过,因为您的代码执行了#34;手动注入&#34;!

答案 1 :(得分:0)

使用@InjectMocks将模拟对象作为依赖项注入到创建的对象(由@InjectMocks标记的对象)。使用构造函数创建对象将违反目的,因为未注入模拟对象,因此来自SUT的所有外部调用都发生在具体对象上。尝试避免通过构造函数调用创建SUT对象。

答案 2 :(得分:0)

  

我们什么时候应该使用@InjectMocks

IHMO:我们不应该使用它。
这支持了许多设计问题,因此我想在这个问题上添加我的POV。

Here is relevant information from the javadoc(重点不是我的):

  

标记应在其上进行注射的字段。

     
      
  • 允许速记模拟和间谍注入。

  •   
  • 最小化重复的模拟和间谍注入。

  •   
     

Mockito将尝试仅通过构造函数注入来注入模拟,   设置器注入或属性注入,顺序和描述如下   下面。如果以下任何策略失败,则Mockito 不会   报告失败;即您必须自己提供依赖项。   ...    Mockito不是依赖注入框架,

用两个词表示:

优点:只需编写更少的代码即可设置模拟依赖项。
缺点:如果未在测试对象中设置某些模拟,则不会失败。

IHMO的优势还掩盖了另一个更加严重的缺陷。

关于优势:
Mockito使用构造函数/设置器/反射来设置被测对象中的模拟对象。问题在于,由您决定设置字段的方式:mockito会尝试另一种方式。

setter方法通常非常冗长,并且提供了依赖项的可变性。因此,开发人员不经常使用它。很有道理
设置Bean依赖项的构造方法要求保持良好的设计并具有相当数量的依赖项:通常不超过4或5。
它需要强大的设计需求。
最后,反射方式是最容易的:只需编写很少的代码,而无需依赖项的可变性。
反射方式是lambda开发人员PVO中最简单的方法,我看到许多团队不了解反射的后果,因为它们不仅在Mockito测试中指定反射方式的依赖性和滥用,而且在设计中也是如此,因为最终InjectMocks允许以某种方式应对它。
您可能会认为,最好不要使用将30个setters声明为依赖项的类或带有30个参数的构造函数来完成,但这也有一个很大的缺点:它倾向于产生不良气味,例如依赖项字段隐藏和作为班级。结果,您的班级复杂,膨胀,难以阅读,难以测试并且易于维护。

真正的现实是,在大多数情况下,正确的解决方案是构造函数注入,该注入可以促进体面的类责任,并因此具有良好的可维护性和可测试性。

关于缺点:
模拟注入不能作为IOC的依赖注入。
Javadoc声明自己:

  

Mockito不是依赖项注入框架,

IOC和DI框架能够并且默认情况下根据需要考虑依赖关系。因此,任何缺少的依赖关系都会在启动时激起fast-fail error
它可以防止很多头痛,使您赢得宝贵的时间。
虽然Mockito注入将默默地忽略未设法注入的模拟。由您来猜测错误原因。 找到问题原因有时很复杂,因为您没有Mockito的任何线索,而所有事情都是Mockito的反思。