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:
I can use OpenEJB's ApplicationComposer or Arquillian to run an embedded container. However, I've never used Arquillian.
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