Java Mockito@InjectMocks未将@Spy分配给正确的字段

Java Mockito@InjectMocks未将@Spy分配给正确的字段,java,spring,spring-boot,junit,mockito,Java,Spring,Spring Boot,Junit,Mockito,我有一个简单的测试用例,其中包含一个模拟对象的间谍列表,然后将其注入到被测试的类中 @Spy List<ValidateRule> ruleServices = new ArrayList<>(); @Mock private EvaluateGroupType evaluateGroupType; @Mock private ValidateServiceRule validateServiceRule; @InjectMocks private Validate

我有一个简单的测试用例,其中包含一个模拟对象的间谍列表,然后将其注入到被测试的类中

@Spy List<ValidateRule> ruleServices = new ArrayList<>();

@Mock
private EvaluateGroupType evaluateGroupType;

@Mock
private ValidateServiceRule validateServiceRule;

@InjectMocks
private ValidateRulesService validateRulesService;

@Before
public void init() throws Exception {
    initMocks(this);
}
private List<ValidateRule> ruleServices;
private List<ResponseTo> responseToList = new ArrayList<>();

@Autowired
public ValidateRulesService(List<ValidateRule> ruleServices) {
    this.ruleServices = ruleServices;
}
@Spy List ruleServices=new ArrayList();
@嘲弄
私有EvaluateGroupType EvaluateGroupType;
@嘲弄
私有ValidateServiceRule ValidateServiceRule;
@注射模拟
私有ValidateRulesService ValidateRulesService;
@以前
public void init()引发异常{
initMocks(这个);
}
但是,在ValidateRulesService类中,列表被注入到错误的列表中

List<Integer> demonstrationList = new ArrayList<>();

@Autowired
private List<ValidateRule> ruleServices;
List demonstrationList=new ArrayList();
@自动连线
私人名单服务;

我还尝试将其作为using构造函数注入,结果是值被注入了两次

List<Integer> demonstrationList = new ArrayList<>();

final private List<ValidateRule> ruleServices;

@Autowired
public ValidateRulesService(List<ValidateRule> ruleServices) {
    this.ruleServices = ruleServices;
}
List demonstrationList=new ArrayList();
最终私人服务清单;
@自动连线
公共ValidateRuleService(列出规则服务){
this.ruleServices=ruleServices;
}

我不希望演示列表在这两种情况下都有任何值。因为根据我在文档中读到的内容,它与rulesService没有相同的名称或类型


我是做错了什么,还是这是莫基托的怪癖?

这里有两件事在起作用。首先,泛型在运行时不存在,因此基本上Mockito会同时看到
列表
实例,然后应该选择一个。第二个问题是,您的字段被声明为
final
,当注入mock/spies时,Mockito将跳过该字段

对于Mockito 1.x(这是使用Spring boot 1.x时的默认设置),据我所知,您无法更改此行为,因此唯一的解决方案是自己在
@SetUp
方法中插入字段:

private ValidateRulesService validateRulesService;
@Spy
private List<ValidateRule> ruleServices = new ArrayList<>();

@Before
public void setUp() {
    // Inject the mocks/spies by yourself
    validateRulesService = new ValidateRulesService(ruleServices);
}
private ValidateRulesService ValidateRulesService;
@间谍
private List ruleServices=new ArrayList();
@以前
公共作废设置(){
//自己注射模拟/间谍
validateRulesService=新的validateRulesService(规则服务);
}
这一点也在本文中提到

另一方面,在Mockito 2.x中,它将使用与spy/mock相同的字段名对字段进行优先级排序,如您在以下内容中所见:

现场注入;mock将首先按类型解析(如果不管名称如何,都会发生单个类型匹配注入),然后,如果存在多个相同类型的属性,则通过字段名和mock名称的匹配来解析

而且:

注意1:如果您有相同类型(或相同擦除)的字段,最好使用匹配字段命名所有@Mock注释字段,否则Mockito可能会混淆,不会发生注入

但是,请注意,如果使用Mockito 2,在注入mock/spies时,它将忽略
final
字段:

同样代表setter或field,它们可以用私有可见性声明,Mockito将通过反射看到它们但是,静态或最终字段将被忽略

(重点是我自己的)


因此,如果您使用Mockito 2.x,如果您删除
final
关键字,它应该正确地注入
ruleServices

我通过修改构造函数注入实现,成功地使它正确地模拟了正确的列表

测试

@Spy
List<ValidateRule> ruleServices = new ArrayList<>();

@Mock
private EvaluateGroupType evaluateGroupType;

@Mock
private ValidateServiceRule validateServiceRule;

private ValidateRulesService validateRulesService;

@Before
public void init() throws Exception {
    initMocks(this);
    ruleServices = Arrays.asList(evaluateGroupType, validateServiceRule);
    validateRulesService = new ValidateRulesService(ruleServices);
}
@Spy
List ruleServices=new ArrayList();
@嘲弄
私有EvaluateGroupType EvaluateGroupType;
@嘲弄
私有ValidateServiceRule ValidateServiceRule;
私有ValidateRulesService ValidateRulesService;
@以前
public void init()引发异常{
initMocks(这个);
ruleServices=Arrays.asList(evaluateGroupType,validateServiceRule);
validateRulesService=新的validateRulesService(规则服务);
}
被测试的班级

@Spy List<ValidateRule> ruleServices = new ArrayList<>();

@Mock
private EvaluateGroupType evaluateGroupType;

@Mock
private ValidateServiceRule validateServiceRule;

@InjectMocks
private ValidateRulesService validateRulesService;

@Before
public void init() throws Exception {
    initMocks(this);
}
private List<ValidateRule> ruleServices;
private List<ResponseTo> responseToList = new ArrayList<>();

@Autowired
public ValidateRulesService(List<ValidateRule> ruleServices) {
    this.ruleServices = ruleServices;
}
私有列表规则服务;
私有列表响应列表=新建ArrayList();
@自动连线
公共ValidateRuleService(列出规则服务){
this.ruleServices=ruleServices;
}

你为什么要窥探这份名单?模拟和设置还不够?@MaciejKowalski虽然这可能会导致更干净的测试,但可能仍然会导致相同的问题。如果使用模拟,则可以指定要注入的字段的名称。如果模拟列表,则无法将模拟对象添加到该列表中。可以进行设置。。我删除了final,但在使用构造函数注入时,它仍然填充两个列表。在这个测试用例中,第二个列表“DeMonstrationList”根本不应该被模仿。@RobertMason如果您删除了
final
关键字并使用了字段注入会怎么样?如果spy和实际字段都命名为
ruleService
,那么现在应该可以使用了。在问题的顶部,您将看到,这是我的第一个实现,使用同名的字段注入。但出于某种原因,它似乎想将其设置为DemonstationList@RobertMason关于名称,我还不知道,但是,请注意类型是相同的,都是
列表
@RobertMason您是对的,我引用的文档来自Mockito 2.x,而Spring boot 1.x默认使用Mockito 1.x。但是,如果您将
mockito-core
版本更改为2.x,它应该可以像我提到的那样工作。我现在在答复中澄清了这一点。