I tried to use specification instead of using findAllByAnything in my Spring Boot. When I tried to implement a test method with the usage of Specification for JUnit, I got an error shown below.
How can I solve the issue?
This part which is shown below in getAdminUsersWithAdmin returns null
Page<AdminUserEntity> adminUserEntitiesByOrganization = adminUserRepository.findAll(specification, listRequest.toPageable());
Here is the relevant method named getAdminUsersWithAdmin
private Page<AdminUser> getAdminUsersWithAdmin(AdminUserListRequest listRequest) {
String organizationId = identity.getOrganizationId();
Specification<AdminUserEntity> specification = Specification
.where(AdminUserSpecifications.hasOrganizationId(organizationId));
Page<AdminUserEntity> adminUserEntitiesByOrganization = adminUserRepository.findAll(specification, listRequest.toPageable());
List<AdminUser> adminUsersByOrganization = adminEntityToAdminMapper.map(adminUserEntitiesByOrganization.getContent());
return Page.of(adminUserEntitiesByOrganization, adminUsersByOrganization);
}
Here is the AdminUserSpecifications shown below
public class AdminUserSpecifications {
public static Specification<AdminUserEntity> hasOrganizationId(String organizationId) {
return (root, query, criteriaBuilder) ->
SearchSpecificationBuilder.eq(criteriaBuilder, root.get("organizationId"), organizationId);
}
}
Here is the eq method in SearchSpecificationBuilder
public static Predicate eq(CriteriaBuilder criteriaBuilder, Path<Object> path, Object value) {
if (value == null) {
return null;
}
return criteriaBuilder.equal(path, value);
}
Here is the test method shown below
@Test
void givenUserListRequest_whenAdminwithRoleIsAdmin_thenReturnAllAdminUsers() {
// Given
AdminUserListRequest mockAdminUserListRequest = new AdminUserListRequestBuilder().withValidValues().build();
AdminUserEntity mockAdminUserEntity = new AdminUserEntityBuilder().withValidFields().build();
List<AdminUserEntity> mockAdminUserEntities = Collections.singletonList(mockAdminUserEntity);
Page<AdminUserEntity> mockPageAdminUserEntities = new PageImpl<>(mockAdminUserEntities);
List<AdminUser> mockAdminUsers = ADMIN_ENTITY_TO_ADMIN_MAPPER.map(mockAdminUserEntities);
Page<AdminUser> mockPageAdminUsers = Page.of(mockPageAdminUserEntities, mockAdminUsers);
UserType userType = UserType.ADMIN;
Specification<AdminUserEntity> specification = Specification.where(AdminUserSpecifications.hasOrganizationId(mockAdminUserEntity.getOrganizationId()));
// When
Mockito.when(identity.getUserType()).thenReturn(userType);
Mockito.when(identity.getOrganizationId()).thenReturn(mockAdminUserEntity.getOrganizationId());
Mockito.when(adminUserRepository.findAll(specification, mockAdminUserListRequest.toPageable()))
.thenReturn(mockPageAdminUserEntities);
Page<AdminUser> pageAdminUsers = adminUserService.getAdminUsers(mockAdminUserListRequest);
// Then
PageBuilder.assertEquals(mockPageAdminUsers, pageAdminUsers);
Mockito.verify(adminUserRepository, Mockito.times(1))
.findAll(specification, mockAdminUserListRequest.toPageable());
}
Here is the error shown below when I run givenUserListRequest_whenAdminwithRoleIsAdmin_thenReturnAllAdminUsers method.
java.lang.NullPointerException: Cannot invoke "org.springframework.data.domain.Page.getContent()" because "adminUserEntitiesByOrganization" is null
Editted
I revised this line shown below
Mockito.when(adminUserRepository.findAll(Mockito.any(Specification.class), Mockito.eq(mockAdminUserListRequest.toPageable()))) .thenReturn(mockPageAdminUserEntities);
I got this issue shown below
Argument(s) are different! Wanted:
adminUserRepository.findAll(
com.admin_user.repository.specification.AdminUserSpecifications$$Lambda$421/0x000000080121b220@7f02251,
Page request [number: 0, size 10, sort: UNSORTED]
);
adminUserRepository.findAll(
com.admin_user.repository.specification.AdminUserSpecifications$$Lambda$421/0x000000080121b220@dffa30b,
Page request [number: 0, size 10, sort: UNSORTED]
);
The problem lies in this line:
Mockito.when(adminUserRepository.findAll(specification, mockAdminUserListRequest.toPageable()))
.thenReturn(mockPageAdminUserEntities);
Your specification is a lambda - Specification.where
returns passed in specification if it is not null, and you pass in a lambda created by hasOrganizationId
.
As the arguments passed in Mockito.when and actual prod code are not equal, Mockito returns default value of Page<AdminUserEntity> adminUserEntitiesByOrganization
- which is null for any non-primitive type.
See the following test:
class AdminUserEntity{}
public class LambdaTest {
public static Specification<AdminUserEntity> hasOrganizationId(String organizationId) {
return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("organizationId"), organizationId);
}
@Test
void lambdasAreNotEqual() {
var spec1a = hasOrganizationId("org1");
var spec1b = hasOrganizationId("org1");
Assertions.assertThat(spec1a).isNotEqualTo(spec1b);
}
}
There are 2 options to solve:
Option 1: relax your expectations about the argument
use Mockito.any(Specification.class)
Mockito.when(adminUserRepository.findAll(Mockito.any(Specification.class), Mockito.eq(mockAdminUserListRequest.toPageable()))) .thenReturn(mockPageAdminUserEntities);
Mockito.eq
)Option 2: use an object that which implements equals (and hashCode for completeness)
A lambda can be modified to concrete subclass of Specification
public class AdminUserSpecifications {
public static class AdminUserEntityHasOrganizationIdSpecification implements Specification<AdminUserEntity> {
private final String organizationId;
public AdminUserEntityHasOrganizationIdSpecification(String organizationId) {
this.organizationId = organizationId;
}
@Override
public Predicate toPredicate(Root<AdminUserEntity> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
return criteriaBuilder.equal( root.get("organizationId"), organizationId);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AdminUserEntityHasOrganizationIdSpecification that = (AdminUserEntityHasOrganizationIdSpecification) o;
return Objects.equals(organizationId, that.organizationId);
}
@Override
public int hashCode() {
return Objects.hash(organizationId);
}
}
public static Specification<AdminUserEntity> hasOrganizationId(String organizationId) {
return new AdminUserEntityHasOrganizationIdSpecification(organizationId);
}
}
In both options, I assume that object returned by mockAdminUserListRequest.toPageable()
correctly implements equals.
If not, add equals or use option 1 or option 2.