【问题标题】:JUnit 5 Not able to mock functions called from function under testJUnit 5无法模拟从被测函数调用的函数
【发布时间】:2020-06-17 04:08:05
【问题描述】:

我是 Junit 5 的新手。被测类中有两个函数,第一个函数调用第二个函数,第二个函数返回一个值,用于第一个函数进行处理。 所以我为这个类创建了一个模拟,但在测试第一个函数时无法模拟第二个函数调用。

第一个函数--exportOpportunityListing() 第二个函数——entityToCsvReport()

public class OpportunityReportServiceImpl extends BaseService implements OpportunityReportService {

    @Value("${nfs.mountPath}")
    private String fileMountPath;

    @Value("${take1.url.host}")
    private String take1HostURL;

    @Autowired
    UsersRepository usersRepository;

    @Autowired
    MailUtil mailUtil;

    @Autowired
    OpportunityJDBCRepository ojdbc;

    @Override
    @Async
    public void exportOpportunityListing(Map<String, Object> paramMap, List<OpportunityCriteria> lfvo,
            String xRemoteUser) {
        try {
            List<OpportunityJDBCDTO> lo = ojdbc.getOppListWithoutPagination(paramMap, lfvo);
            List<OpportunityReport> exportData = lo.parallelStream().map(this::entityToCsvReport)
                    .collect(Collectors.toList());
            CsvCustomMappingStrategy<OpportunityReport> mappingStrategy = new CsvCustomMappingStrategy<>();
            mappingStrategy.setType(OpportunityReport.class);
            String dirPath = fileMountPath + REPORT_PATH;
            File fileDir = new File(dirPath);
            if (!fileDir.exists()) {
                FileUtils.forceMkdir(fileDir);
            }
            String pathWithoutExtension = dirPath + "opportunity_data_"
                    + LocalDateTime.now().format(DateTimeFormatter.ofPattern(YYYYMMDDHHMMSS));
            File reportFile = new File(pathWithoutExtension + EXTENSION_CSV);
            Writer writer = new PrintWriter(reportFile);
            StatefulBeanToCsv<OpportunityReport> beanToCsv = new StatefulBeanToCsvBuilder<OpportunityReport>(writer)
                    .withMappingStrategy(mappingStrategy).build();
            beanToCsv.write(exportData);
            writer.close();
            String zipFilePath = pathWithoutExtension + EXTENSION_ZIP;
            ZipUtil.zip(reportFile, zipFilePath);
            Users remoteUser = usersRepository.findByUsername(xRemoteUser)
                    .orElseThrow(() -> new Take1Exception(ErrorMessage.USER_NOT_FOUND_WITH_USERNAME, xRemoteUser));
            Mail mail = Mail.builder().to(new String[] { remoteUser.getEmail() })
                    .model(MailModel.builder().name(remoteUser.getName())
                            .body("Please find attached the opportunity report you requested.").build())
                    .subject("Opportunity Report").attachments(Arrays.asList(new File(zipFilePath))).build();
            mailUtil.sendMail(mail);
            Files.delete(reportFile.toPath());
        } catch (IOException | CsvDataTypeMismatchException | CsvRequiredFieldEmptyException e) {
            throw new Take1Exception(ErrorMessage.INTERNAL_SERVER_EXCEPTION, e);
        }

    }

    public OpportunityReport entityToCsvReport(OpportunityJDBCDTO o) {
        OpportunityReport or = modelMapper.map(o, OpportunityReport.class);
        or.setCurrency("USD");
        or.setOnline(Boolean.TRUE.equals(o.getIsOnline()) ? "YES" : "NO");
        return or;
    }

}

这是我的 JUnit 测试用例。

class OpportunityReportServiceImplTest {

    @InjectMocks
    OpportunityReportServiceImpl opportunityReportServiceImpl;

    @Autowired
    OpportunityReportServiceImpl ors;

    @Mock
    OpportunityJDBCRepository ojdbc;

    @Mock
    UsersRepository usersRepository;

    @Mock
    MailUtil mailUtil;

    @Mock
    ModelMapper mp;

    String username = "anandabhishe";
    String nfusername = "ananda";
    Mail mail;
    List<OpportunityJDBCDTO> lo = new ArrayList<OpportunityJDBCDTO>();
    List<OpportunityReport> lor = new ArrayList<OpportunityReport>();

    @BeforeEach
    void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        ReflectionTestUtils.setField(opportunityReportServiceImpl, "fileMountPath", ".");
        ReflectionTestUtils.setField(opportunityReportServiceImpl, "take1HostURL", "");
        lo.add(new OpportunityJDBCDTO());
        lor.add(new OpportunityReport());

    }

    @Test
    void testExportOpportunityListing() throws IOException {
        OpportunityReport or = new OpportunityReport();
        or.setCurrency("USD");
        or.setOnline("Yes");

        when(ojdbc.getOppListWithoutPagination(getParamMap(), getOppCriteria())).thenReturn(lo);
        when(usersRepository.findByUsername(username)).thenReturn(Optional.of(getUser()));
        doNothing().when(mailUtil).sendMail(mail);
        // doNothing().when(opportunityReportServiceImpl).entityToCsvReport(oj);
        when(opportunityReportServiceImpl.entityToCsvReport(getOpportunityJDBCDTO())).thenReturn(or);
        opportunityReportServiceImpl.exportOpportunityListing(getParamMap(), getOppCriteria(), username);
        assertTrue(true);
        FileUtils.forceDelete(new File("." + REPORT_PATH));
    }

    private Map<String, Object> getParamMap() {
        return new HashMap<String, Object>();
    }

    private List<OpportunityCriteria> getOppCriteria() {
        List<OpportunityCriteria> loc = new ArrayList<>();
        loc.add(new OpportunityCriteria());
        return loc;
    }

    private OpportunityJDBCDTO getOpportunityJDBCDTO() {
        OpportunityJDBCDTO oj = new OpportunityJDBCDTO();
        oj.setIsOnline(true);
        oj.setApplicationCount(2);
        oj.setCost(200);
        oj.setCountryCode("in");
        oj.setCreationDate(LocalDateTime.now());
        oj.setEndDate(LocalDate.now());
        oj.setLocation("test");
        oj.setOpportunityId(123);
        oj.setOpportunityStatus("test");
        oj.setOpportunityStatusId(1);
        oj.setOpportunityTitle("test");
        oj.setOpportunityType("test");
        oj.setOpportunityTypeColor("test");
        oj.setOpportunityTypeId(1);
        oj.setPublishedAt(LocalDateTime.now());
        oj.setPublishedBy("test");
        oj.setPublishedByUserName("test");
        oj.setRegistrationUrl("test");
        oj.setStartDate(LocalDate.now());
        oj.setSummary("test");
        oj.setUserEmail("test");
        oj.setUserFullName("test");
        oj.setUserId(1);
        oj.setUserName("test");
        oj.setVendorName("test");
        return oj;
    }

    private Users getUser() {
        Users user = new Users();
        return user;
    }

}

当调用测试类中的行时,我得到空指针异常:

when(opportunityReportServiceImpl.entityToCsvReport(getOpportunityJDBCDTO())).thenReturn(or);

【问题讨论】:

  • 我会建议你使用构造函数注入(因为它使提供模拟和测试数据更容易)和你解构你的方法,以便它做的更少:例如,打开一个文件以返回一个 @ 987654324@ 在一种方法中,然后将Writer 传递给另一种方法以执行输出。例如,这使您可以提供 StringWriter,这可以使测试更简洁。
  • 该类目前无法更改,它已经写很久了,还有其他类遵循方法解构。可悲的是,我们在开发完成后开始编写测试用例:(
  • 测试类有注解吗?使用 Mockito,您至少应该拥有 @Extension(MocktioExtension.class)。此外,我不明白你为什么要模拟你的被测类的公共方法?当你对你的类进行单元测试时,你通常会模拟对外部类的调用,而不是类本身的方法。
  • @rieckpil 当我没有模拟第二种方法时,我得到了一个 NPE,所以我想模拟它。
  • 我认为 NPE 可能来自没有在 entityToCsvReport 内模拟 modelMapper

标签: java junit5 spring-test


【解决方案1】:

我错过了模拟第二个函数中使用的模型映射器存根,在我添加之后,测试通过了。

OpportunityReport or = new OpportunityReport();
        OpportunityJDBCDTO oj = new OpportunityJDBCDTO();
        when(ojdbc.getOppListWithoutPagination(any(HashMap.class), anyList())).thenReturn(lo);
        when(usersRepository.findByUsername(anyString())).thenReturn(Optional.of(getUser()));
        doNothing().when(mailUtil).sendMail(mail);
        doReturn(or).when(mp).map(oj, OpportunityReport.class);
        opportunityReportServiceImpl.exportOpportunityListing(getParamMap(), getOppCriteria(), username);
        assertTrue(true);

【讨论】:

    【解决方案2】:

    发生这种情况是因为 opportunityReportServiceImpl 不是模拟对象 - 它是您尝试测试的对象,但您尝试对它的方法进行存根,就好像它是模拟对象一样。

    我建议您不要尝试存根您尝试测试的对象的方法。但如果必须,您需要将其声明为@Spy。然后要存根它,您需要doReturn/when 语法而不是when/thenReturn。这可能看起来像

    doReturn(lo).when(ojdbc).getOppListWithoutPagination(getParamMap(), getOppCriteria());
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-12-02
      • 1970-01-01
      • 2021-08-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-11-17
      • 1970-01-01
      相关资源
      最近更新 更多