FlexTradeUKLtd / jfixture

JFixture is an open source library based on the popular .NET library, AutoFixture
MIT License
105 stars 22 forks source link

Override properties only works if setter exists #45

Open tsuharesu opened 6 years ago

tsuharesu commented 6 years ago

According to the wiki:

fixture.customise().propertyOf(Order.class, "size", 123); This looks for a setter called setSize on the Order type and uses the value 123 instead of the default.

Is there a way to make this work in the constructor? I have a Kotlin data class with a property defined as val. This property is in the constructor but has no setter.

richkeenan commented 6 years ago

Hi @tsuharesu thanks for the question. I believe you're asking if you can customise a particular argument in a constructor - if so then unfortunately this isn't possible.

I'd absolutely love to be able to do this but it requires knowing the name of the constructor argument through reflection, which Java 8 is better at, but at the time of writing this lib I couldn't see a reliable way of doing this. Maybe Kotlin has a way of doing this?

A couple workarounds I can think of are,

1.

// customise the instance and manually create it using JFixture 
// for each default arg and do your own thing for the other args.
// This will quickly get unwieldy though
fixture.customise().lazyInstance(Order.class, () -> new Order(fixture.create(String.Long), "custom string"));`
fixture.create(Order.class);

2.

// customise the type of the argument you're overriding.
// Obviously this will apply for all instances of that type though
fixture.customise().lazyInstance(String.class, () -> "my order id");
fixture.create(Order.class);

If you know how this could be done I'd be very happy to take a PR as it's a feature that's been requested a few times.

Thanks

pwfcurry commented 6 years ago

Additionally, except with Kotlin data classes, there is no guarantee a contructor parameter named "size" corresponds to the "size" field.

You could use the copy constructor:

fixture.customise().lazyInstance(Order.class, () -> new JFixture().create(Order.class).copy(size = 123));
fixture.create(Order.class);

However you will have to use a different instance of JFixture to create the initial object. The intercept customisation does not return the new value, otherwise would be perfect for this (this is a change we should make - unless there is already a way to do this?).

TWiStErRob commented 6 years ago

We solved this by setting the field via reflection. Kotlin data class properties are just simple final fields. So we generate the fixture object and then setField(fixtObj, "size", 123); this can be combined with intercept as well.

TWiStErRob commented 6 years ago

@richkeenan Kotlin tries to make the constructor parameter names accessible:

https://stackoverflow.com/a/36093832/253468

Accessible around: FooBar::class.constructors.first().parameters.first().name

public abstract val name: String? defined in kotlin.reflect.KParameter Name of this parameter as it was declared in the source code, or null if the parameter has no name or its name is not available at runtime. Examples of nameless parameters include this instance for member functions, extension receiver for extension functions or properties, parameters of Java methods compiled without the debug information, and others.

Edit: you can also access this from Java!

JvmClassMappingKt.getKotlinClass(FooBar.class).getConstructors().iterator().next().getParameters().get(0).getName()
richkeenan commented 6 years ago

@TWiStErRob That's very cool, exactly what we'd need to get it working. KFixture anyone? 😉

TWiStErRob commented 6 years ago

This means you can detect if Kotlin is on classpath and try to use this class (e.g. compileOnly dependency with Class.forName check before usage) no need for a separate library or increasing dependency count of JFixture.

tsuharesu commented 6 years ago

@richkeenan @TWiStErRob Thanks for your help, really! I think Ex. 1 (using lazyInstance) can help for now. Would be the same as using KodeIn to create instances of a class. I like the idea of KFixture 😂