【问题标题】:When should we use @InjectMocks?我们什么时候应该使用@InjectMocks?
【发布时间】:2017-09-13 19:07:56
【问题描述】:

我已经阅读了很多关于@Mock 和@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

【问题讨论】:

  • 还有一个提示:请教我正是不是这个网站的内容。下次注意你的措辞!
  • 感谢您的及时采纳。我还能做些什么来让我的回答在你眼中也值得投票吗?

标签: testing junit mockito


【解决方案1】:

如果您不提供更多代码,则无法回答您的实际问题(您显示的代码并未解释您声称观察到的结果)。

关于基本问题:您可能不应该使用@InjectMocks;它的核心问题之一是:如果注入失败,Mockito 确实向您报告错误。

换句话说:您已经通过了单元测试,并且有关字段的一些内部细节发生了变化……并且您的单元测试中断了;但你不知道为什么......因为模拟框架没有告诉你将模拟推入被测类的“初始”步骤突然失败。如需进一步阅读,请参阅here

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

编辑:我想我现在明白你的问题了。 @InjectMocks 的想法是将模拟对象注入一些被测对象

但是:在这两种情况下,您都是手动进行的:

 service.setUserDao(dao);

含义:如果 injecting 工作正常(并且没有 Mockito 未报告的问题),那么使用该注释的示例应该在以下情况下工作你删除那一行。而没有 @InjectMocks 的测试用例应该 fail 没有该行!

换句话说:您的测试用例都通过了,因为您的代码执行了“手动注入”!

【讨论】:

  • 我同意这是一种风格或个人品味。我现在正在研究这个概念,但仍然无法理解。所以,我正在为是否应该使用它而苦苦挣扎。
  • 建议:修改您的问题并创建一个真实的minimal reproducible example,以便我们了解您的代码为何在这两种情况下都有效。也许这可以帮助您确定该注释是否为您增加了价值。
  • 我更新了我的示例,它更接近我当前的工作。然而,我不确定@InjectMocks 到底做了什么。我只需要一个模拟对象并隔离被测类的依赖关系。我希望被测类的行为完全符合它的行为。所以,我更喜欢使用原始方法(它的新实例)。
  • 哇!!我试图删除行 service.setUserDao(dao);并且测试也通过了!我的下一个问题是 mockito 怎么知道我的 @mock 引用了被测类的特定数据成员?如果被测类有两个同一个类的私有数据成员(例如两个 UserDao )怎么办?
  • 那将是另一个问题。但可能通过阅读 Mockito 文档很容易回答。我认为图书馆只是使用各种启发式方法来“识别”最佳“匹配”。
【解决方案2】:

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

【讨论】:

  • 如果模拟对象不是通过构造函数调用注入的,我的模拟对象返回值是如何工作的(通过when().thenReturn()子句)?
  • 也许你的具体实现也返回相同的值。
  • @sudheersingh @InjectMocks 注释不会创建对象。 In 只会将模拟注入到对象中。
【解决方案3】:

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

IHMO:我们不应该使用它。
这有利于很多设计问题,我想在这个问题上添加我的 POV。

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

标记应执行注入的字段。

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

  • 最大限度地减少重复的模拟和间谍注入。

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

两个字:

优势:您可以编写更少的代码来设置模拟依赖项。
缺点:如果在被测对象中未设置一些模拟,则不会失败。

IHMO 的优点还隐藏着另一个更严重的缺点。

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

setter 方法通常非常冗长,并且提供了依赖项的可变性。所以开发者不会经常使用它。这是有道理的。
设置 bean 依赖项的构造方法需要保持良好的设计和相当数量的依赖项:一般不超过 4 或 5 个。
它需要强大的设计紧迫性。
最后,反射方式是最简单的:编写代码少,依赖项不可变。
反射方式是 lambda 开发人员 PVO 中最简单的,我看到许多团队没有意识到反射的后果,在他们的 Mockito 测试以及他们的设计中指定依赖和滥用反射方式,因为最终InjectMocks 允许应对以某种方式。
您可能会认为不使用将 30 个 setter 声明为依赖项的类或具有 30 个参数的构造函数完成也很好,但这也有一个很大的缺点:它有利于不良气味,例如依赖项字段隐藏和将许多依赖项声明为字段实例班上。因此,您的课程复杂、臃肿、难以阅读、难以测试且易于维护。

实际情况是,在大多数情况下,正确的解决方案是构造函数注入,它促进了良好的类责任以及良好的可维护性和可测试性。

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

Mockito 不是依赖注入框架,

IOC 和 DI 框架能够并在默认情况下根据需要考虑依赖关系。所以任何缺失的依赖都会在启动时引发fast-fail error
它可以避免很多麻烦,让您赢得宝贵的时间。
而 Mockito 注入将默默地忽略未设法注入的模拟。您可以自行猜测错误原因。
查找问题原因有时很复杂,因为您没有从 Mockito 获得任何线索,而这一切都是通过 Mockito 的反射完成的。

【讨论】:

    猜你喜欢
    • 2021-09-07
    • 1970-01-01
    • 2011-07-04
    • 2021-12-29
    • 2011-07-17
    • 2022-01-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多