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.
Advertisement
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