如何将测试方法与其约束验证器(其中一些应该被模拟掉)一起进行单元测试?

How to unit test methods together with its constraint validators (which some should be mocked out)?

我的应用程序有一个由 CDI 应用程序作用域 bean 组成的服务层:

@ApplicationScoped
@Transactional
public class PostService {
    @Inject private PostRepository postRepo;
    @Inject private UserRepository userRepo;
    @Inject private SectionRepository sectionRepo;
    @Inject private LoggedInUser loggedInUser;

    public PostDto getPost(@PostExists int id){
        Post p = postRepo.findById(id);
        //create post DTO from p
        return post;
    }

    public void delete(@PostExists int id){
        postRepo.remove(postRepo.findById(id));
    }

    public int newPost(@NotBlank @Max(255) String title,
                       @Max(2000) String body,
                       @SectionExists String sectionName){
        User user = userRepo.getByName(loggedInUser.getUsername());
        Section section = sectionRepo.getByName(sectionName);

        Post post = new Post();
        post.setTitle(title);
        post.setContent(body == null || body.isBlank() ? "" : body);
        post.setAuthor(user);
        post.setSection(section);
        post.setType(TEXT);

        return postRepo.insert(post).getId();
    }

} 

调用方法时,拦截器(在我的例子中来自 Apache BVal BValInterceptor.class)通过检查注释并相应地验证参数来检查方法契约是否得到遵守。

如您所见,有一些自定义约束,例如 @SectionExists@PostExists 可能会影响数据库:

public class SectionExistsValidator implements ConstraintValidator<SectionExists, String> {
    @Inject SectionRepository sectionRepo;

    @Override
    public void initialize(SectionExists constraintAnnotation) {}

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        return (sectionRepo.getByName(value) != null);
    }
}



public class PostExistsValidator implements ConstraintValidator<PostExists, Integer> {
    @Inject PostRepository postRepo;

    @Override
    public void initialize(PostExists constraintAnnotation) {}

    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext context) {
        return (postRepo.findById(value) != null);
    }
}

我想做的是对我的业务方法(getpostdeletenewPost一起进行单元测试它的验证器。可能命中数据库的验证器应该被模拟(或者它们的依赖性应该被模拟)。

我怎样才能做到这一点?我怎样才能让注入(和模拟注入)在单元测试中为验证器工作?

这是我正在使用的:

我可以使用 OpenEJB's ApplicationComposer 或 Arquillian 来 运行 一个嵌入式容器。但是,我从未使用过 Arquillian。

最后我选择了this really cool library (cdimock) that does exactly what i needed: put the mocks in a custom CDI scope so that the same mock instances can be injected in other beans inside the test case. Such thing can also be achievable with cdi-unit @Produces @Mock 注释(虽然我没有亲自尝试过,因为它只支持焊接)

这是我的测试class'代码:

@RunWithApplicationComposer(mode = ExtensionMode.PER_EACH)
@ExtendWith({MockitoExtension.class, CdiMocking.class})
@MockitoSettings(strictness = LENIENT)
@Classes(cdi = true,
         value={PostService.class},
         cdiInterceptors = BValInterceptor.class,
         cdiStereotypes = CdiMock.class)
public class PostServiceTest {

    @Mock SectionRepository sectionRepository;
    @Mock PostRepository postRepository;
    @Mock UserRepository userRepository;
    @Inject PostService service;   

    @BeforeEach
    void setUp() {}

    @AfterEach
    void tearDown() {}

    @Test
    public void noSectionFoundNewPost(){
        String sectionName = "idontexist";
        when(sectionRepository.getByName(sectionName)).thenReturn(null);
        assertThrows(ConstraintViolationException.class,
                () -> service.newPost("title", "body", sectionName));
    }
}

在代码中,我使用的是 OpenEJB 的 Application Composer,但我可以轻松切换到任何嵌入式 CDI 容器