如何模拟已注入依赖项的 super class

how to mock super class that has injected dependencies

我有一个使用 Spring MVC 的项目。 我正在尝试为服务模块编写单元测试, 这是在项目的架构中。 所有服务 classes 都从名为 "BaseService" 的超级 class 扩展而来。 BaseService是这样的:

public abstract class BaseService {

private static final Logger  logger = LoggerFactory.getLogger(BaseService.class);

@Autowired(required = true)
private HttpServletRequest   request;

@Autowired
private ReloadableResourceBundleMessageSource messageSource;

/*
 * Injecting Mapper
 */
@Resource
private Mapper mapper;
...

public <T extends BaseBVO, S extends BaseVO> T voToBvo (S vo, Class<?     extends BaseBVO> bvoClass) {

    if (vo != null)
    {
        return (T) mapper.map(vo , bvoClass);
    }
    else
    {
        return null;
    }
}

现在我在服务模块中有一个使用方法的方法:

"voToBvo (S vo, Class<?     extends BaseBVO> bvoClass")

像这样:

public List<AdcOptionBVO> findOptionByAyantDroitIdAndProduitId (Long idAyantDroit, String idProduit) {

    ....

        list = listVoToBvo(adcList , AdcOptionBVO.class);
        logger.info("Returning List of ayrp count= {}" , list.size());

    return list;
}

我的测试是这样的:

@RunWith(MockitoJUnitRunner.class)
public class AdcServiceImplTest{

@Mock
private Mapper mapper;

    @Test
public void shouldSuccessFindOptionByAyantDroitIdandProduitId() {
    //Given
    List<AdcVO> adcVOList = new ArrayList<>();
    adcVOList.add(new AdcVO());

    List<AdcOptionBVO> listAdcOptionBVO = new ArrayList<>();
    listAdcOptionBVO.add(new AdcOptionBVO());

    List<BaseBVO> baseBVOs = new ArrayList<>();

    //When
    when(repository
            .finAdcByOptionOrderByRUAndPrio(anyLong(), anyString())).thenReturn(adcVOList);

    when(baseService.listVoToBvo(adcVOList,  AdcOptionBVO.class)).thenReturn(baseBVOs);


    //Then
    assertEquals(adcService
            .findOptionByAyantDroitIdAndProduitId(anyLong(), anyString()).size(), adcVOList.size());

}
}

我在调用映射器时在 BaseService.java 中得到一个 java.lang.NullPointerException。

 @Resource
 private Mapper mapper;

映射器为空!

我想模拟方法:

listVoToBvo(adcList , AdcOptionBVO.class);

请帮忙。

由于您的测试当前已设置,none 已注入基础 class 中的注入项目。 您可以使用两种技术来解决此问题:

  1. 运行 使用 Spring 测试运行程序(名为 SpringJUnit4ClassRunner.class)。这对于超出范围的集成测试很好。
  2. 手动进行注射。使用反射并将 BaseService class 中的映射器设置为模拟对象。

使用技巧2。

创建一个用于测试的实用程序,它使用反射设置数据成员。 执行 google 搜索 "set parent class variable reflection java" 或类似的东西,你会找到示例反射 code.d 该实用程序应包装反射代码并为数据成员名称、class 对象、对要在其中设置值的对象的引用以及执行反射所需的任何其他内容获取参数。

另一种选择是寻找反射实用程序(google、apache 和 spring)应该都有一个。

这是一个 (non-functioning) 示例:

public static void setFieldObject(
    final String fieldName,
    final Object fieldValue,
    final Object targetObject,
    final Class targetClass)
throws
    NoSuchFieldException,
    IllegalAccessException
{
    final Field targetField;

    targetField = targetClass.getDeclaredField(fieldName);
    targetField.setAccessible(true);

    targetField.set(
        targetObject,
        fieldValue);
}

实际上你的代码不太清楚,但是使用 Mockito 模拟自动装配的字段并不那么复杂,你只需按照正确的步骤操作即可。

假设我们要为 BaseService 创建单元测试:

public class BaseServiceTest {

    private static final BaseVO TEST_BaseVO = //intialize here...;
    private static final BaseBVO TEST_BaseBVO = //intialize here...;  

    private static final BaseBVO TEST_BaseBVO_RESULT = //intialize here...;

    @InjectMocks
    private BaseService baseService;

    @Mock
    private HttpServletRequest request;

    @Mock
    private ReloadableResourceBundleMessageSource messageSource;

    @Mock
    private Mapper mapper;

    @BeforeMethod
    public void initMocks(){
        //This line of code is so important!! to initialize annotated fields. without it these fields would be null.
        //Or just annotate this class with @RunWith(MockitoJUnitRunner.class)

        MockitoAnnotations.initMocks(this);

        //Here all fields annotated with @Mock or @Spy should be initialize and injected into BaseService.
    }

    @Test
    public void voToBvo_should_return_null_when_vo_is_null(){

        //Call testing method
        BaseBVO result = baseService.voToBvo(null, TEST_BaseBVO.class);

        //Assertions
        Assert.assertNull(result); 
    }

    @Test
    public void voToBvo_should_not_return_null_when_vo_is_not_null(){

        //Mock calls
        Mockito.doReturn(TEST_BaseBVO_RESULT).when(mapper).map(TEST_BaseVO, TEST_BaseBVO.class)

        //Call testing method
        BaseBVO result = baseService.voToBvo(TEST_BaseVO, TEST_BaseBVO.class);

        //Assertions
        Assert.assertNotNull(result); 
        Assert.assertEquals(TEST_BaseBVO_RESULT, result);
    }
}

您可能觉得这个单元测试很愚蠢(实际上很愚蠢 :p ),但它描述了使用 Mockito 创建成功的 UT 的基本步骤以及如何防止 NPE。

解决方案是为带有注入参数的 abstarct class 添加构造函数,在我的例子中(映射器),然后在服务构造函数中的服务调用 super(mapper); 所以在测试中我模拟了 Mapper 映射器,然后我在 @Before 中实例化了我的 class,所以映射器作为 Mock 而不是 null 传递给 abstarct class ..它对我来说很好用.我希望是克莱尔

我会建议您,首先更改您的 BaseService 文件。 更改@Autowired的写法如下

private HttpServletRequest request;  
@Autowired(required = true)
public void setSpellChecker( HttpServletRequest request ){
   this.request = request;
} 

@Autowired
private ReloadableResourceBundleMessageSource messageSource;
public void setSpellChecker( ReloadableResourceBundleMessageSource messageSource ){
   this.messageSource = messageSource;
}

当您这样做时,现在您可以轻松实现 test-cases 依赖。

注入模拟后,尝试使用 @Before 初始化它们。

例如:

@Mock
private HttpServletRequest request;

@Mock
private Mapper mapper;

@InjectMocks
private BaseService baseService;

@Before
public void initMocks(){    
    MockitoAnnotations.initMocks(this);
}

对我有用。