naver / fixture-monkey

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

스프링 시큐리티의 User를 상속받은 코틀린 클래스 생성 오류 #1031

Closed jurogrammer closed 3 weeks ago

jurogrammer commented 1 month ago

Describe the bug

org.springframework.security.core.userdetails.User를 상속받은 코틀린 클래스 생성시 오류가 발생합니다.

스프링 프로젝트와 엮여 있기 때문에 혼자 해보려고 했으나, 에러 로그만으로는 파악하기 어려워 버그 리포트 드립니다.

Your environment

Steps to reproduce

gradle

plugins {
    kotlin("jvm") version "2.0.0"
    kotlin("plugin.spring") version "1.9.24"
    id("org.springframework.boot") version "3.3.2"
    id("io.spring.dependency-management") version "1.1.6"
}

group = "com.example"
version = "0.0.1-SNAPSHOT"

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-security")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
    testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
    testImplementation("org.springframework.security:spring-security-test")
    testRuntimeOnly("org.junit.platform:junit-platform-launcher")
    testImplementation("com.navercorp.fixturemonkey:fixture-monkey-starter-kotlin:1.0.23")
    testImplementation(kotlin("test"))
}

kotlin {
    compilerOptions {
        freeCompilerArgs.addAll("-Xjsr305=strict")
    }
}

tasks.withType<Test> {
    useJUnitPlatform()
}

code

import com.navercorp.fixturemonkey.FixtureMonkey
import com.navercorp.fixturemonkey.kotlin.KotlinPlugin
import com.navercorp.fixturemonkey.kotlin.giveMeOne
import org.junit.jupiter.api.RepeatedTest
import org.springframework.security.core.authority.AuthorityUtils
import org.springframework.security.core.userdetails.User

class FixtureTest {
    @RepeatedTest(10)
    fun `User를 상속받은 클래스는 에러를 던진다`() {
        val fixture = FixtureMonkey.builder()
            .plugin(KotlinPlugin())
            .build()

        // cannot invoke "java.lang.Integer.intValue()" because the return value of ...
        fixture.giveMeOne<ServiceUser>()
    }
}

data class ServiceUser(val nickname: String) : User(
    nickname,
    "password",
    AuthorityUtils.createAuthorityList("NORMAL")
)

Expected behaviour

에러 없이 객체 생성

Actual behaviour

높은 확률로 에러 발생

Cannot invoke "java.lang.Integer.intValue()" because the return value of "java.lang.reflect.InvocationHandler.invoke(Object, java.lang.reflect.Method, Object[])" is null
java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because the return value of "java.lang.reflect.InvocationHandler.invoke(Object, java.lang.reflect.Method, Object[])" is null
    at jdk.proxy3/jdk.proxy3.$Proxy20.hashCode(Unknown Source)
    at java.base/java.util.HashMap.hash(HashMap.java:338)
    at java.base/java.util.HashMap.put(HashMap.java:610)
    at java.base/java.util.HashSet.add(HashSet.java:221)
    at com.navercorp.fixturemonkey.api.context.MonkeyGeneratorContext.isUniqueAndCheck(MonkeyGeneratorContext.java:42)
    at com.navercorp.fixturemonkey.api.generator.ArbitraryGeneratorContext.isUniqueAndCheck(ArbitraryGeneratorContext.java:137)
    at com.navercorp.fixturemonkey.api.introspector.SetIntrospector.lambda$null$0(SetIntrospector.java:66)
    at com.navercorp.fixturemonkey.api.arbitrary.FilteredCombinableArbitrary.combined(FilteredCombinableArbitrary.java:74)
    at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)
    at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1625)
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
    at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921)
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:682)
    at com.navercorp.fixturemonkey.api.arbitrary.ContainerCombinableArbitrary.combined(ContainerCombinableArbitrary.java:48)
    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.api.arbitrary.ObjectCombinableArbitrary.lambda$combined$0(ObjectCombinableArbitrary.java:51)
    at java.base/java.util.HashMap.forEach(HashMap.java:1421)
    at com.navercorp.fixturemonkey.api.arbitrary.ObjectCombinableArbitrary.combined(ObjectCombinableArbitrary.java:50)
    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.api.arbitrary.FilteredCombinableArbitrary.combined(FilteredCombinableArbitrary.java:73)
    at com.navercorp.fixturemonkey.resolver.ResolvedCombinableArbitrary.combined(ResolvedCombinableArbitrary.java:77)
    at com.navercorp.fixturemonkey.resolver.DefaultArbitraryBuilder.sample(DefaultArbitraryBuilder.java:489)
    at com.navercorp.fixturemonkey.FixtureMonkey.lambda$giveMe$3(FixtureMonkey.java:158)
    at java.base/java.util.stream.StreamSpliterators$InfiniteSupplyingSpliterator$OfRef.tryAdvance(StreamSpliterators.java:1358)
    at java.base/java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:129)
    at java.base/java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:527)
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:513)
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
    at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921)
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:682)
    at com.navercorp.fixturemonkey.FixtureMonkey.giveMe(FixtureMonkey.java:166)
    at com.navercorp.fixturemonkey.FixtureMonkey.giveMeOne(FixtureMonkey.java:174)
    at FixtureTest.User를 상속받은 클래스는 에러를 던진다(FixtureTest.kt:26)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183)
    at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)
    at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179)
    at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)
    at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183)
    at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)
    at java.base/java.util.stream.IntPipeline$1$1.accept(IntPipeline.java:180)
    at java.base/java.util.stream.Streams$RangeIntSpliterator.forEachRemaining(Streams.java:104)
    at java.base/java.util.Spliterator$OfInt.forEachRemaining(Spliterator.java:711)
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
    at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150)
    at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173)
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596)
    at java.base/java.util.stream.ReferencePipeline$7$1.accept(ReferencePipeline.java:276)
    at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1625)
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
    at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150)
    at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173)
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
seongahjo commented 1 month ago

@jurogrammer 안녕하세요.

1.0.x에서는 KotlinPlugin에서 제공하는 생성 방식이 자바 타입과 코틀린 타입을 분리하지 않고 모두 호환하고 있습니다. 코틀린에서도 자바에서와 같이 타입의 필드를 모두 생성하려다가 이 같은 이슈가 발생한 것으로 보입니다.

임시로 FixtureMonkeyBuilder에 아래 옵션을 추가하시면 해결될 것으로 보입니다.

.defaultPropertyGenerator(
  KotlinPropertyGenerator(
      javaDelegatePropertyGenerator = ConstructorPropertiesArbitraryIntrospector.PROPERTY_GENERATOR
  )
)

1.1.x 부터는 언어마다 생성 방식을 분리하여 이 같은 버그가 없을 예정입니다.

감사합니다.

jurogrammer commented 4 weeks ago

정상동작 확인했습니다. 감사합니다!