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

java.sql.Timestamp not recognized #865

Closed praveenfun2 closed 10 months ago

praveenfun2 commented 10 months ago

Describe your bug

Consider the below code

// java

import java.sql.Timestamp;

public class ABC {

    private Timestamp created;
}

// java

// kotlin
var fixtureMonkey: FixtureMonkey = FixtureMonkey.builder()
            .objectIntrospector(FailoverIntrospector(
                    listOf(
                            BuilderArbitraryIntrospector.INSTANCE,
                            ConstructorPropertiesArbitraryIntrospector.INSTANCE,
                            BeanArbitraryIntrospector.INSTANCE
                    )
            ))
            .defaultNullInjectGenerator(
                    DefaultNullInjectGenerator(
                            1.0,
                    false,
                    false,
                    false,
                    Collections.emptySet(),
                    Collections.emptySet()
            ))
            .build()
val b2 = fixtureMonkey.giveMeBuilder(ABC::class.java)
        val obj2 = b2.sample()
// kotlin

Your environment

Steps to reproduce

Execute the above code

Expected behaviour

obj2 should be created with created as null

Actual behaviour

Throws error Failed to generate type "ABC"

seongahjo commented 10 months ago

@praveenfun2 Hello, java.sql.Timestamp type is not supported by default.

You have to use pushArbitraryIntrospector option to support it.

If you are having trouble using an option, feel free to ask!

praveenfun2 commented 10 months ago

Hi @seongahjo, since the value that is set, is null, should it really matter what the data type is ? As we are actually not generating a non null value, do we still need a custom Introspector ?

praveenfun2 commented 10 months ago

I also tried

.addExceptGenerateClass(Timestamp::class.java)

but it didn't work

seongahjo commented 10 months ago

@praveenfun2 Oh, sorry. I did not explain it clearly.

No, if you do not care about the value you generate, You do no need to use the custom ArbitraryIntrospector

The addExceptGenerateClass option needs to be fixed, it now uses on ArbitraryIntrospector now, Instead of using addExceptGenerateClass, it is better to use the fallback ArbitraryIntrospector to generate null value.

.objectIntrospector(FailoverIntrospector(
                    listOf(
                            BuilderArbitraryIntrospector.INSTANCE,
                            ConstructorPropertiesArbitraryIntrospector.INSTANCE,
                            BeanArbitraryIntrospector.INSTANCE,
                            ArbitraryIntrospector { ArbitraryIntrospectorResult(CombinableArbitrary.from(null as Any?)) }
                    )
            ))
praveenfun2 commented 10 months ago

Consider the below code

// java

@Builder
public class ABC {

    private Timestamp created;
    private Child displayDetails;
}

interface Child {
    String getPaymentMethodVariant();

    class Card implements Child {
        private String paymentMethodVariant;

        @Override
        public String getPaymentMethodVariant() {
            return paymentMethodVariant;
        }
    }
}
// java

// kotlin
var fixtureMonkey: FixtureMonkey = FixtureMonkey.builder()
            .objectIntrospector(FailoverIntrospector(
                    listOf(
                            BuilderArbitraryIntrospector.INSTANCE,
                            ConstructorPropertiesArbitraryIntrospector.INSTANCE,
                            BeanArbitraryIntrospector.INSTANCE
                    )
            ))
            .pushExactTypeArbitraryIntrospector(Timestamp::class.java) {
                ArbitraryIntrospectorResult(CombinableArbitrary.from { null })
            }
            .defaultNullInjectGenerator(
                    DefaultNullInjectGenerator(
                            1.0,
                    false,
                    false,
                    false,
                    Collections.emptySet(),
                    Collections.emptySet()
            ))
            .build()
val b2 = fixtureMonkey.giveMeBuilder(ABC::class.java)
        val obj2 = b2.sample()
// kotlin

I am expecting that the obj2.displayDetails would be null but instead of that it gets assigned a proxy. Attaching screenshot. Screenshot 2024-01-10 at 3 52 13 PM Can you help me with how I can make sure default value for displaydetails is always null.

seongahjo commented 10 months ago

@praveenfun2 This is because Fixture Monkey generates an anonymous object for the interface as default.

Please add a plugin to disable it. It will be disabled as default since 1.1.x.

.plugin(InterfacePlugin().useAnonymousArbitraryIntrospector(false))

Or You can address this issue with adding an option like below.

.objectIntrospector(FailoverIntrospector(
                    listOf(
                            BuilderArbitraryIntrospector.INSTANCE,
                            ConstructorPropertiesArbitraryIntrospector.INSTANCE,
                            BeanArbitraryIntrospector.INSTANCE,
                            ArbitraryIntrospector { ArbitraryIntrospectorResult(CombinableArbitrary.from(null as Any?)) }
                    )
            ))
praveenfun2 commented 10 months ago

@seongahjo Thanks, this seems to fix it. I have another question.

Consider the below code

// java

@Builder
public class ABC {

    @Builder.Default
    private boolean created = false;
}

// java

// kotlin
var fixtureMonkey: FixtureMonkey = FixtureMonkey.builder()
            .objectIntrospector(FailoverIntrospector(
                    listOf(
                            BuilderArbitraryIntrospector.INSTANCE,
                            ConstructorPropertiesArbitraryIntrospector.INSTANCE,
                            BeanArbitraryIntrospector.INSTANCE,
                           ArbitraryIntrospector { ArbitraryIntrospectorResult(CombinableArbitrary.from(null as Any?)) }
                    )
            ))
            .defaultNullInjectGenerator(
                    DefaultNullInjectGenerator(
                            1.0,
                    false,
                    false,
                    false,
                    Collections.emptySet(),
                    Collections.emptySet()
            ))
            .build()
val b2 = fixtureMonkey.giveMeBuilder(ABC::class.java)
        val obj2 = b2.sample()
// kotlin

Sometimes the value for created is false & sometimes its true. My expectation is it'll always remain the same as initialized in the variable, unless modified by manipulators

seongahjo commented 10 months ago

@praveenfun2 Hello. In this case, BuilderArbitraryIntrospector will be used. BuilderArbitraryIntrospector generate an instance of ABC type by a builder method. The methods of the builder class will generate properties. So created will be populated randomly even if @Builder.Default is present.

Currently, the property not randomly populated is not supported except for the default constructor argument. It will be supported in version 1.1.0.

If you want to use an initialized value, you have to use the `set' API to fix it. Thank you.

praveenfun2 commented 10 months ago

@seongahjo Ok. I am facing another issue now. Consider the below code

// java

@Data
@Builder
@With
@AllArgsConstructor
@NoArgsConstructor
@Slf4j
public class WWW {

    private Map<String, ?> templateAttributes;

}

// java

// kotlin
var fixtureMonkey: FixtureMonkey = FixtureMonkey.builder()
            .objectIntrospector(FailoverIntrospector(
                    listOf(
                            BuilderArbitraryIntrospector.INSTANCE,
                            ConstructorPropertiesArbitraryIntrospector.INSTANCE,
                            BeanArbitraryIntrospector.INSTANCE,
                           ArbitraryIntrospector { ArbitraryIntrospectorResult(CombinableArbitrary.from(null as Any?)) }
                    )
            ))
            .defaultNullInjectGenerator(
                    DefaultNullInjectGenerator(
                            1.0,
                    false,
                    false,
                    false,
                    Collections.emptySet(),
                    Collections.emptySet()
            ))
            .build()
val b2 = fixtureMonkey.giveMeBuilder(WWW::class.java)
        val obj2 = b2.sample()
// kotlin

obj2 is coming as null sometimes but sometimes it's being initialized as empty object. My expectation is it'll always be empty object

seongahjo commented 10 months ago

@praveenfun2 I can not reproduce the issue, obj2 is always not null. Can you check if it is a valid example?

praveenfun2 commented 10 months ago

Hmm, that's strange. I have executed the same code. But when I changed "private Map<String, ?> templateAttributes" to "private Map<String, Object> templateAttributes", I am getting obj2 as non-null. But even in this case, templateAttributes had random values in it. But after using nullableContainer to true, templateAttributes started coming as null. @seongahjo In your system, is templateAttributes null or having random values ? I am expecting that templateAttributes would be null even when I use "?" instead of "Object"

praveenfun2 commented 10 months ago

Also, since primitives can't be set to null, is there a way to define default values for them irrespective of classes where they are used ?

seongahjo commented 10 months ago

@praveenfun2 Oh, I see. I did not think wildcard is actually wildcard. Since it might be different depending on the situation, it is fixed as Object. It may be customizable in the next version. In this case, You can fix it by adding an option like below.

pushExactTypeArbitraryIntrospector<Object>{
    ArbitraryIntrospectorResult(CombinableArbitrary.from(null as Any?)) 
}

Also, since primitives can't be set to null, is there a way to define default values for them irrespective of classes where they are used ?

You can add a new custom plugin for resolving primitives. The javaTypeArbitraryGeneratorSet option in the plugin would be the one you want.

.plugin {
            it.javaTypeArbitraryGeneratorSet { ... }
}
praveenfun2 commented 10 months ago

Thanks!! I think all the issues are resolved now for me. We can close this one out.