naver / fixture-monkey

Let Fixture Monkey generate test instances including edge cases automatically
https://naver.github.io/fixture-monkey
Apache License 2.0
575 stars 90 forks source link

NPE occurs in a class with a specific property #762

Closed kjh5833 closed 1 year ago

kjh5833 commented 1 year ago

Describe the bug

안녕하세요! 라이브러리 잘 사용하고 있습니다. 테스트 작성 중 특정 필드(mHeader, isGlobal)를 가진 class 들에서 NPE 가 발생하고, 정상동작 하지 않아 이슈를 올리게 되었습니다. 이유 및 회피 방안이 있는지 궁금합니다.

--

We have a little problem. NPE occurs in a class with a specific field(mHeader, isGlobal). I wonder if there is a reason or a way to avoid this.

Your environment

Steps to reproduce

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
public class Valid {

    private String mheader;
    private boolean isglobal;
}

@Getter
@Setter
@ToString
public class Invalid {

    private String mHeader;
    private boolean isGlobal;
}
class FixtureTest extends Specification {

// 통과
    def 'Valid test'() {
        given:
        def given = FixtureMonkey.create().giveMeOne(Valid.class)

        when:
        System.out.println("given :: " + given)

        then:
        noExceptionThrown()
    }

// 실패
    def 'Invalid test'() {
        given:
        def given = FixtureMonkey.create().giveMeOne(Invalid.class)

        when:
        System.out.println("given :: " + given)

        then:
        noExceptionThrown()
    }
}

Expected behaviour

Test PASS

Actual behaviour

NPE occurred

java.lang.NullPointerException
    at com.navercorp.fixturemonkey.api.introspector.BeanArbitraryIntrospector.lambda$null$0(BeanArbitraryIntrospector.java:76)
    at java.base/java.util.HashMap.forEach(HashMap.java:1337)
    at com.navercorp.fixturemonkey.api.introspector.BeanArbitraryIntrospector.lambda$combine$1(BeanArbitraryIntrospector.java:70)
    at com.navercorp.fixturemonkey.api.arbitrary.ObjectCombinableArbitrary.combined(ObjectCombinableArbitrary.java:54)
    at com.navercorp.fixturemonkey.api.arbitrary.NullInjectCombinableArbitrary.combined(NullInjectCombinableArbitrary.java:46)
    at com.navercorp.fixturemonkey.api.arbitrary.TraceableCombinableArbitrary.combined(TraceableCombinableArbitrary.java:42)
    at com.navercorp.fixturemonkey.resolver.ArbitraryResolver$1.combined(ArbitraryResolver.java:115)
    at com.navercorp.fixturemonkey.api.arbitrary.FilteredCombinableArbitrary.combined(FilteredCombinableArbitrary.java:68)
    at com.navercorp.fixturemonkey.resolver.DefaultArbitraryBuilder.sample(DefaultArbitraryBuilder.java:420)
    at com.navercorp.fixturemonkey.FixtureMonkey.lambda$giveMe$2(FixtureMonkey.java:166)
    at java.base/java.util.stream.StreamSpliterators$InfiniteSupplyingSpliterator$OfRef.tryAdvance(StreamSpliterators.java:1360)
    at java.base/java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:127)
    at java.base/java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:502)
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:488)
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
    at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913)
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578)
    at com.navercorp.fixturemonkey.FixtureMonkey.giveMe(FixtureMonkey.java:174)
    at com.navercorp.fixturemonkey.FixtureMonkey.giveMeOne(FixtureMonkey.java:182)
    at com.중략.FixtureTest.Invalid test(FixtureTest.groovy:23)
seongahjo commented 1 year ago

@kjh5833 님 안녕하세요.

create를 통해 생성한 Fixture Monkey 인스턴스는 기본 BeanArbitraryIntrospector를 사용해 객체를 생성합니다. BeanArbitraryIntrospector에서는 빈 생성자setter를 사용해 객체를 만들고 있습니다. setter를 찾을 때 JavaBeans 스펙을 기준으로 조회하고 있습니다. 따라서 isGlobal인 필드는 setter의 이름이 setIsGlobal 이여야 하고, mHeadersetmHeader 이여야 합니다. 참조1 참조2

기본적인 롬복 설정에서는 isGlobal의 setter를 setGlobal, mHeader의 setter를 setMHeader로 생성합니다. 이런 이유로, 현재 setter를 못찾아서 NPE가 나고 있습니다.

해결방안은 두 가지가 있을 것 같습니다.

첫 번째 해결방안

다른 ArbitraryIntrospector를 사용한다. 지금 보여주신 객체의 예만 봤을 때는 FieldReflectionArbitraryIntrospector가 적합해보입니다.

다음과 같이 설정하시면 ArbitraryIntrospector를 변경할 수 있습니다.

FixtureMonkey.builder()
    .objectIntrospector(FieldReflectionArbitraryIntrospector.INSTANCE)
    .build();

ArbitraryIntrospector에 대한 설명과 종류는 문서를 참조해주세요.

두 번째 해결방안

롬복 설정을 추가한다.

  1. lombok.config에 lombok.accessors.javaBeansSpecCapitalization=true를 추가한다.
  2. isGlobal 필드에 @Accessors(fluent=true)를 사용한다.

세 번째 해결방안

필드의 이름을 JavaBeans에 맞게 변경한다.

시도해보시고, 문제가 있으면 다시 질문 주세요. 감사합니다~~

kjh5833 commented 1 year ago

@seongahjo 님 안녕하세요! 원인 및 여러 해결방안까지 정확한 답변 감사드립니다~! 첫번째 방안으로 해결하였습니다! 감사합니다!