Search code examples
javaspringspring-bootmockito

Java Spring ManyToOne JoinColumn entity is null in service test


When I run the test code, I get the following exception: java.lang.NullPointerException: Cannot invoke "com.project.clickit.entity.DormitoryEntity.toDTO()" because "this.dormitoryEntity" is null

DormitoryEntity

@Setter
@Getter
@Builder
@Table(name = "dormitory")
@NoArgsConstructor
//@AllArgsConstructor
@Entity
public class DormitoryEntity {
    @Id
    @Column(name = "dormitory_id")
    private String id;

    @Column(name = "dormitory_name")
    private String name;

    @Builder
    public DormitoryEntity(String id, String name) {
        this.id = id;
        this.name = name;
    }

    public DormitoryDTO toDTO() {
        return DormitoryDTO.builder()
                .id(this.id)
                .name(this.name)
                .build();
    }
}

FacilityEntity

@Setter
@Getter
@Builder
@Table(name = "facility")
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class FacilityEntity {
    @Id
    @Column(name = "facility_id")
    private String id;

    @Column(name = "facility_name")
    private String name;

    @Column(name = "facility_info")
    private String info;

    @Column(name = "facility_open")
    private Integer open;

    @Column(name = "facility_close")
    private Integer close;

    @Column(name = "facility_img")
    private String img;

    @Column(name = "facility_terms")
    private String terms;

    @Column(name = "facility_extension_limit")
    private Integer extensionLimit;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "facility_dormitory")
    private DormitoryEntity dormitoryEntity;

    public FacilityEntity(String id, String name, String info, Integer open,
                          Integer close, String img, String terms, Integer extensionLimit) {
        this.id = id;
        this.name = name;
        this.info = info;
        this.open = open;
        this.close = close;
        this.img = img;
        this.terms = terms;
        this.extensionLimit = extensionLimit;
    }

    public FacilityDTO toDTO() {
        return FacilityDTO.builder()
                .id(this.id)
                .name(this.name)
                .info(this.info)
                .open(this.open)
                .close(this.close)
                .img(this.img)
                .terms(this.terms)
                .extensionLimit(this.extensionLimit)
                .dormitoryDTO(this.dormitoryEntity.toDTO())
                .build();
    }
}

FacilityRepository

@Query("SELECT new FacilityEntity(f.id, f.name, f.info, f.open, f.close, f.img, f.terms, f.extensionLimit, f.dormitoryEntity) FROM FacilityEntity f where f.id = :id")
    FacilityEntity findByFacilityId(@Param("id") String id);

FacilityService

@Transactional
    public FacilityDTO findById(String id) {
        return facilityRepository.findByFacilityId(id).toDTO();
    }

Test Code

@Slf4j
@ExtendWith({MockitoExtension.class})
public class FacilityServiceTest {
    @Mock
    private FacilityRepository facilityRepository;

    @InjectMocks
    private FacilityService facilityService;

    @Test
    void findByIdTest(){

        given(facilityRepository.findByFacilityId("test_facility")).willReturn(new FacilityEntity());

        FacilityDTO result = facilityService.findById("test_facility");

        assertThat(result).isNotNull;
    }
}

How can I solve this problem?

I tried the following things to solve this problem: add @Transactional annotation after @Test annotation(In detail, @Transactional(propagation = Propagation.SUPPORT)), edit fetchType LAZY to EAGER

*(Edit) When a Get request is sent using Postman, findById in FacilityService operates normally. So, This seems to happen because FacilityRepository is a Mock object, not a real object. But I still don't know how to solve this problem.


Solution

  • Problem is, the mock behavior is to return a facility but doesn't initialise the dormatory value;

    .willReturn(new FacilityEntity());

    As your service findById method invokes FacilityEntity.toDTO() -> DormatoryEntity.toDTO() and the reference to dormatory is null you end up with the NPE.

    Initialising that value should fix it, something like this:

      var facility = new FacilityEntity();
      var dormatory = new DormitoryEntity();
      // init any data you want 
      facility.setDormitoryEntity(dormatory);
      given(facilityRepository.findByFacilityId("test_facility"))
         .willReturn(facility);
    

    Alternatively, if your data is likely to have facilities with no dormatories you can null-check the DormatoryEntity in your FacilityDTO toDTO() method.

    You may also consider hooking your tests up to a in-memory (or even test container) database as this can help highlight other hibernate related issues.