ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • TestFixture Monkey 객체 필드가 null로 생성되는 이슈해결
    Spring/Trouble Shooting 2024. 10. 20. 01:34

    TestFixture Monkey를 통해 테스트 객체를 생성하는 방식을 구현 중에 다음과 같이 객체를 생성하고 테스트를 수행하는데 NPE가 발생하는 문제가 발생했습니다.

    public class AnimalHospitalTestFixture extends DomainTestFixture {
        public static AnimalHospital createAnimalHospital() {
    
            ArbitraryBuilder<HospitalInfo> hospitalInfoArbitraryBuilder = SUT.giveMeBuilder(HospitalInfo.class)
                    .set(javaGetter(HospitalInfo::getName), Arbitraries.strings().alpha().ofLength(10))
                    // ...
                    
            return SUT.giveMeBuilder(AnimalHospital.class)
                    .setNull(javaGetter(AnimalHospital::getId))
                    .set(javaGetter(AnimalHospital::getHospitalInfo), hospitalInfoArbitraryBuilder)
                    .sample();
        }
        //...
    }

    객체를 생성하는 간략하게 수정한 코드는 위와 같습니다.

     

    여기서 AnimalHospital의 Embeddable 객체인 HospitalInfo의 name 값을 조회했을 때 null이 발생하는 경우가 발생했습니다.

    public abstract class DomainTestFixture {
        protected static final FixtureMonkey SUT = FixtureMonkey.builder()
                .objectIntrospector(ConstructorPropertiesArbitraryIntrospector.INSTANCE)
                .defaultNotNull(true)
                .build();
    }

    Fixture Monkey Introspector의 설정값은 위와 같이 생성자 방식으로 되어있었습니다.

     

    해당 방법으로 record로 되어있는 일반 DTO들은 잘만 생성되었습니다.

     

    그런데 왜 Entity class만 생성이 안되는건지....

     

    따로 defaultNotNull을 true로 해두었기에 null 값이 일부러 발생한 상황은 아니었기에 Introspector의 방식의 문제가 아닐까 해서

    Entity class의 생성자를 확인해 봤습니다.

    @NoArgsConstructor(access = AccessLevel.PROTECTED)
    @Getter
    @Embeddable
    public class HospitalInfo {
    
        @Column(name = "name")
        private String name;
    	//...
    
        @Builder
        private HospitalInfo(
                String name,
                //...
        ) {
            this.name = name;
            //...
        }
    }

    기본 생성자의 경우 JPA proxy가 Reflection을 통해 객체를 생성하기에 protected로 설정해 두었고

    파라미터를 가진 생성자의 경우 롬복을 활용하여 Builder 패턴으로 값을 생성하도록 하였기에 private로 설정해 둔 상태였습니다.

     

    여기서 아... 설마 public 생성자가 없기 때문에 FixtureMonkey가 객체 생성을 못하고 있는 건가...? 해서 private을 public으로 변경하여 시도를 해봤습니다.

    계속 시도하다가 NPE 빨간 글씨에 익숙해요 이젠

     

    그럼 롬복으로 Builder를 사용할 수 있으니

    공식문서에 명시된 BuilderArbitraryIntrospector.INSTANCE로 Introspector 값을 변경하면 되는 거 아니야? 했지만

    또 보게 되더라고요

    https://velog.io/@park2348190/Lombok-Builder%EC%9D%98-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC#%EC%83%9D%EC%84%B1%EC%9E%90-%EB%A0%88%EB%B2%A8-%EC%96%B4%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98

     

    Lombok @Builder의 동작 원리

    보일러플레이트 메서드(getter/setter, constructor 등)를 직접 작성하지 않아도 대신 작성해주는 Lombok를 최근에 많이 활용하고 있다. 그나마 setter 메서드같은 경우는 값을 변경시키는 메서드는 그 목

    velog.io


    이 글 보고 왜 안된 건지 알게 되었습니다. 클래스 자체에 빌더를 만들어 주는 게 아니더군요.

    여기까지는 실패한 방식이었고 

     

    객체 필드에 Reflection으로 아예 새로운 인스턴스를 만들어주는

    FieldReflectionArbitraryIntrospector.INSTANCE 방법을 통해

     

    드디어 NPE 에서 벗어날 수 있었습니다.

     

    하지만 기존 record DTO 객체에서 잘 되던 방식을 Entity 때문에 바꿔야 하는가...? 이건 또 아닌 거 같더라고요.

    이를 대비해서 Naver 개발자 분들께서 미리 만들어주신 방법이

    FailoverArbitraryIntrospector

    으로 여러 클래스마다 다른 방식으로 생성을 해야 하는 경우 앞에서 실패해도 이어서 다른 Introspector로 생성 시도를 해주는 방식이 있기에

    public abstract class DomainTestFixture {
        protected static final FixtureMonkey SUT = FixtureMonkey.builder()
                .objectIntrospector(new FailoverIntrospector(
                        Arrays.asList(
                                FieldReflectionArbitraryIntrospector.INSTANCE,
                                ConstructorPropertiesArbitraryIntrospector.INSTANCE
                        )
                ))
                .defaultNotNull(true)
                .build();
    }

    이렇게 수정해서 사용하기로 결정하였습니다.

     

    여기서 저는

    BeanArbitraryIntrospector
    방식은 처음부터 거들떠도 안 봤는데 그 이유 중 하나가

    인자가 없는 생성자(또는 기본생성자)와 setter가 있어야 합니다.

    라길래 Entity class에 Setter를 설정하면 큰일 날 것 같아서 바로 걸렀습니다.

    댓글

Designed by black7375.