Skip to content
Advertisement

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

My app has a service layer which is composed by CDI applications scoped beans:

@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();
    }

} 

When a method gets called, an interceptor (in my case BValInterceptor.class from Apache BVal) checks if the method contract is respected by checking the annotations and validating the parameters accordingly.

As you can see, there are some custom constraints like @SectionExists, @PostExists that may hit the database:

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);
    }
}

What I’d like to do is to unit test my business methods (getpost, delete, newPost) together with its validators. The validators that may hit the database should be mocked (or their dependency should be mocked).

How can I achieve this? How could I make injections (and mock injections) work for validators in unit tests?

Here what I’m using:

  • TomEE 8.0.8
  • Apache BVal for Bean Validation JSR 303/JSR380 (included in TomEE)
  • Apache OpenWebBeans for CDI (included in TomEE)
  • JUnit 5
  • Mockito

I can use OpenEJB’s ApplicationComposer or Arquillian to run an embedded container. However, I’ve never used Arquillian.

Answer

In the end I went for 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 annotations (Although i haven’t tried it personally since it only supports Weld)

This is my test class’ code:

@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));
    }
}

In the code i’m using OpenEJB’s Application Composer but i can easily switch to any embedded CDI container

Advertisement