【问题标题】:Unable to invoke method annotated with @PostConstruct when mocked using Mockito使用 Mockito 模拟时无法调用使用 @PostConstruct 注释的方法
【发布时间】:2018-04-13 12:56:28
【问题描述】:

我正在尝试使用 Junit 和 Mockito 为存储库层类编写单元测试。 我模拟了提供 NamedParameterJdbcOperations 的基类,并尝试注入到 repo 类中。 在 repo 类中,我们从类路径中的文件加载 sql 查询。这是在使用@PostConstruct 注释的方法中完成的。 在尝试测试 repo 的方法时,它无法找到或加载查询并因此抛出 NullPointerException。

需要有关如何处理这种情况的帮助/建议。

PS:我不允许更改 repo 类的实现。

附上repo和测试类的代码供参考。

RepositoryImpl.java

@Repository
public class RepositoryImpl extends AppJdbcImpl implements
    Repository {

private static final StudentMapper STUDENT_ROW_MAPPER = new StudentMapper();
private static final CourseMapper COURSE_ROW_MAPPER = new CourseMapper();

@Value("classpath:sql/sql1.sql")
private Resource sql1;
private String query1;

@Value("classpath:sql/sql2.sql")
private Resource sql2;
private String query2;

public RepositoryImpl() { }

public RepositoryImpl(NamedParameterJdbcOperations jdbc) {
    super(jdbc);
}

@PostConstruct
public void setUp() {
    query1 = loadSql(sql1);
    query2 = loadSql(sql2);
}

public Iterable<Course> findCoursesByStudentId(int studentId) throws
    DataAccessException {

    try {
        return jdbc().queryForObject(query1,
            ImmutableMap.of("studentId", studentId),
            COURSE_ROW_MAPPER);

    } catch (EmptyResultDataAccessException emptyResult) {
        return null;
    } catch (DataAccessException e) {
        // Need to create exception classes and throw specific exceptions
        throw e;
    }

}

public Iterable<Student> findStudentsByCourseId(int courseId) throws DataAccessException {

    try {

        return jdbc().query(query2,
            ImmutableMap.of("courseId", courseId),
            STUDENT_ROW_MAPPER);

    } catch (DataAccessException e) {
        // Need to create exception classes and throw specific exceptions
        throw e;
    }

}

private String loadSql(Resource resource) {
    try {
        return CharStreams.toString(new InputStreamReader(resource.getInputStream()));
    } catch (IOException e) {
        return null;
    }
}

}

RespositoryImplTest.java

@RunWith(MockitoJUnitRunner.class)
public class RepositoryImplTest {
@Mock
private NamedParameterJdbcOperations jdbc;

@Mock
private ResultSet resultSet;

@Mock
private StudentMapper studentMapper;

@Mock
private CourseMapper CourseMapper;

@InjectMocks
private RepositoryImpl repository;

private Student student1;
private Student student2;

private Course course1;
private Course course2;

@Before
public void setUp() throws Exception {
    MockitoAnnotations.initMocks(this);

    course1 = new Course(1, "Karate");
    course2 = new Course(2, "Riding");
    course8 = new Course(8, "Swimming");
    List<Course> courseList = Arrays.asList(course1, course2, course8);

    student1 = new Student(1, "Chuck", "Norris", 27, new Arrays.asList(course1, course2));
    student2 = new Student(2, "Bruce", "Lee", 54, new Arrays.asList(course1, course8));
    List<Student> studentList = Arrays.asList(student1, student2);

    when(jdbc.queryForObject(Matchers.anyString(), anyMap(),
        isA(StudentMapper.class)))
        .thenAnswer(new Answer() {
            @Override
            public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
                Object[] args = invocationOnMock.getArguments();
                int queryParam = Integer.parseInt(args[0].toString());

                Iterable<Credentials> result = studentList.stream()
                .filter(d -> d.getId() == queryParam)
                .collect(Collectors.toList());

                return result;
            }
        });
}

@Test
public void findCoursesByStudentId() {
    Iterable<Course> result = repository.findCoursesByStudentId(1);
    assertNotNull(result);
}

}

在 repo 类中,由于 query1 为空而引发异常。

需要帮助才能正确解决问题。

谢谢,巴鲁

【问题讨论】:

  • 创建实例后为什么不调用respository.setUp()
  • 您没有使用 spring 运行器运行测试,也没有让 spring 创建和注入存储库。所以 PostConstruct 不被任何人调用。您可以自己调用它,但 @Value 注释字段将为空(同样,因为存储库不是由 Spring 创建的)。阅读有关测试的 Spring 文档。
  • @JB Nizet,你是​​对的。由于字段上的 @Value 注释,调用 setUp() 对测试没有帮助。我将再次参考文档,但必须使用 Junit 和 Mockito 进行测试。
  • 使用模拟测试 DAO 是,恕我直言,一个巨大的反模式。您要测试的是您的查询工作正常。并不是说您使用任何字符串和任何地图调用 queryForObject。这增加了代码覆盖率,但实际上并没有测试任何东西。因此,使用测试数据设置您的数据库(使用 DbSetup 或 DBUnit,或者只是您自己的 JDBC 代码),然后调用您的存储库方法,并检查它是否返回了预期返回的内容。
  • 找到了解决我的情况的方法。使用org.springframework.test.util.ReflectionTestUtils 并设置字段。这帮助我避免了 NPE,并为 @PostConstruct 注释方法中构建的私有字段设置了预期值。这就是设置字段的方式ReflectionTestUtils.setField(repository, "query1", "query1"); ReflectionTestUtils.setField(repository, "query2", "query2");

标签: java mocking mockito postconstruct


【解决方案1】:
@RunWith(MockitoJUnitRunner.class)

你用 mockito starter 开始测试,而不是 spring starter。这意味着春天没有给你豆子。 Mockito 入门者对 PostConstruct 注释一无所知。

您可以在 sturUp junit 方法或测试方法中自己调用 PostConstruct 方法。

【讨论】:

  • 做到了,但这并没有帮助。抛出 NPE,因为它不处理带有 @Value 的字段。
  • 找到了解决我的情况的方法。使用org.springframework.test.util.ReflectionTestUtils 并设置字段。这帮助我避免了 NPE,并为 @PostConstruct 注释方法中构建的私有字段设置了预期值。这就是设置字段的方式ReflectionTestUtils.setField(repository, "query1", "query1"); ReflectionTestUtils.setField(repository, "query2", "query2");
猜你喜欢
  • 2014-03-24
  • 1970-01-01
  • 2017-06-03
  • 2017-01-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-20
  • 1970-01-01
相关资源
最近更新 更多