Skip to content

Commit

Permalink
Add constructor strategy (#50)
Browse files Browse the repository at this point in the history
Fixes #49
  • Loading branch information
mattmook authored Jun 6, 2020
1 parent 8defe69 commit 9e37870
Show file tree
Hide file tree
Showing 15 changed files with 655 additions and 1 deletion.
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,42 @@ ktype kotlin.String →
Success(5878ec34-c30f-40c7-ad52-c15a39b44ac1)
```

#### constructorStrategy

By default when the library generates an instance of a class it picks a
constructor at random. This can be overridden by setting a constructor
strategy.

```kotlin
val fixture = kotlinFixture {
constructorStrategy(ModestConstructorStrategy)
}
```

The following strategies are built in:

- [RandomConstructorStrategy](fixture/src/main/kotlin/com/appmattus/kotlinfixture/decorator/constructor/RandomConstructorStrategy.kt),
order constructors by random.

- [ModestConstructorStrategy](fixture/src/main/kotlin/com/appmattus/kotlinfixture/decorator/constructor/ModestConstructorStrategy.kt),
order constructors by the most modest constructor first. i.e. fewer
parameters returned first.

- [GreedyConstructorStrategy](fixture/src/main/kotlin/com/appmattus/kotlinfixture/decorator/constructor/GreedyConstructorStrategy.kt),
order constructors by the most greedy constructor first. i.e. greater
parameters returned first.

- [ArrayFavouringConstructorStrategy](fixture/src/main/kotlin/com/appmattus/kotlinfixture/decorator/constructor/ArrayFavouringConstructorStrategy.kt),
order constructors selecting those with the most parameters of
Array<*> before any other.

- [ListFavouringConstructorStrategy](fixture/src/main/kotlin/com/appmattus/kotlinfixture/decorator/constructor/ListFavouringConstructorStrategy.kt),
order constructors selecting those with the most parameters of List<*>
before any other.

It is also possible to define and implement your own constructor
strategy by implementing `ConstructorStrategy` and applying it as above.

## Kotest support

[Kotest](https://github.com/kotest/kotest/) supports
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright 2020 Appmattus Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.appmattus.kotlinfixture.decorator.constructor

import com.appmattus.kotlinfixture.Context
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.full.starProjectedType

/**
* Order constructors, selecting those with the most parameters of Array<*> before any other. Any other constructors
* are returned with the most modest constructors first.
*/
object ArrayFavouringConstructorStrategy : ConstructorStrategy {

@Suppress("EXPERIMENTAL_API_USAGE")
private val arrayTypes = listOf(
Array<Any>::class,

BooleanArray::class,
ByteArray::class,
DoubleArray::class,
FloatArray::class,
IntArray::class,
LongArray::class,
ShortArray::class,
CharArray::class,
UByteArray::class,
UIntArray::class,
ULongArray::class,
UShortArray::class
).map { it.starProjectedType }

override fun constructors(context: Context, obj: KClass<*>): Collection<KFunction<*>> {
return ModestConstructorStrategy.constructors(context, obj).sortedByDescending {
it.parameters.count { parameter ->
(parameter.type.classifier as KClass<*>).starProjectedType in arrayTypes
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright 2020 Appmattus Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.appmattus.kotlinfixture.decorator.constructor

import com.appmattus.kotlinfixture.config.ConfigurationBuilder

/**
* Set the ordering strategy used to try class constructors when generating an instance.
* Default: [RandomConstructorStrategy]
*/
@Suppress("unused")
fun ConfigurationBuilder.constructorStrategy(strategy: ConstructorStrategy) {
strategies[ConstructorStrategy::class] = strategy
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright 2020 Appmattus Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.appmattus.kotlinfixture.decorator.constructor

import com.appmattus.kotlinfixture.Context
import kotlin.reflect.KClass
import kotlin.reflect.KFunction

/**
* Strategy used to determine order to try class constructors when generating an instance.
*/
interface ConstructorStrategy {
/**
* Returns [obj] constructors in the order to try when generating an instance.
*/
fun constructors(context: Context, obj: KClass<*>): Collection<KFunction<*>>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright 2020 Appmattus Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.appmattus.kotlinfixture.decorator.constructor

import com.appmattus.kotlinfixture.Context
import kotlin.reflect.KClass
import kotlin.reflect.KFunction

/**
* Order constructors by the most greedy constructor first, i.e. greater parameters returned first. This means that if a
* default constructor exists, it will be the last one returned.
*
* In case of two constructors with an equal number of parameters, the ordering is unspecified.
*/
object GreedyConstructorStrategy : ConstructorStrategy {
override fun constructors(context: Context, obj: KClass<*>): Collection<KFunction<*>> {
return obj.constructors.sortedByDescending { it.parameters.size }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright 2020 Appmattus Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.appmattus.kotlinfixture.decorator.constructor

import com.appmattus.kotlinfixture.Context
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.full.starProjectedType

/**
* Order constructors, selecting those with the most parameters matching List<*> before any other. Any other
* constructors are returned with the most modest constructors first.
*/
object ListFavouringConstructorStrategy : ConstructorStrategy {
override fun constructors(context: Context, obj: KClass<*>): Collection<KFunction<*>> {
return ModestConstructorStrategy.constructors(context, obj).sortedByDescending {
it.parameters.count { parameter ->
(parameter.type.classifier as KClass<*>).starProjectedType == List::class.starProjectedType
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright 2020 Appmattus Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.appmattus.kotlinfixture.decorator.constructor

import com.appmattus.kotlinfixture.Context
import kotlin.reflect.KClass
import kotlin.reflect.KFunction

/**
* Order constructors by the most modest constructor first, i.e. fewer parameters returned first. This means that if a
* default constructor exists, it will be the first one returned.
*
* In case of two constructors with an equal number of parameters, the ordering is unspecified.
*/
object ModestConstructorStrategy : ConstructorStrategy {
override fun constructors(context: Context, obj: KClass<*>): Collection<KFunction<*>> {
return obj.constructors.sortedBy { it.parameters.size }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2020 Appmattus Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.appmattus.kotlinfixture.decorator.constructor

import com.appmattus.kotlinfixture.Context
import kotlin.reflect.KClass
import kotlin.reflect.KFunction

/**
* Order constructors at random.
*/
object RandomConstructorStrategy : ConstructorStrategy {
override fun constructors(context: Context, obj: KClass<*>): Collection<KFunction<*>> {
return obj.constructors.shuffled(context.random)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ package com.appmattus.kotlinfixture.resolver
import com.appmattus.kotlinfixture.Context
import com.appmattus.kotlinfixture.Unresolved
import com.appmattus.kotlinfixture.createUnresolved
import com.appmattus.kotlinfixture.decorator.constructor.ConstructorStrategy
import com.appmattus.kotlinfixture.decorator.constructor.RandomConstructorStrategy
import com.appmattus.kotlinfixture.strategyOrDefault
import kotlin.reflect.KClass

internal class ClassResolver : Resolver, PopulateInstance {
Expand All @@ -33,7 +36,9 @@ internal class ClassResolver : Resolver, PopulateInstance {
callingClass = obj
)

val results = obj.constructors.shuffled().map { constructor ->
val constructorStrategy = context.strategyOrDefault<ConstructorStrategy>(RandomConstructorStrategy)

val results = constructorStrategy.constructors(context, obj).map { constructor ->
val result = context.resolve(KFunctionRequest(obj, constructor))
if (result !is Unresolved) {
return if (populatePropertiesAndSetters(callContext, result)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright 2020 Appmattus Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.appmattus.kotlinfixture.decorator.constructor

import com.appmattus.kotlinfixture.ContextImpl
import com.appmattus.kotlinfixture.config.Configuration
import com.nhaarman.mockitokotlin2.doReturn
import com.nhaarman.mockitokotlin2.mock
import kotlin.reflect.KClass
import kotlin.test.Test
import kotlin.test.assertEquals

class ArrayFavouringConstructorStrategyTest {

@Test
fun `Order constructors with greatest array parameter count first followed by remaining modest first`() {
val context = ContextImpl(Configuration())

val shuffledConstructors = mock<KClass<MultipleConstructors>> {
on { constructors } doReturn MultipleConstructors::class.constructors.shuffled()
}

val result = ArrayFavouringConstructorStrategy.constructors(context, shuffledConstructors).map {
val emptyParameters = List<Any?>(it.parameters.size) { null }
(it.call(*emptyParameters.toTypedArray()) as MultipleConstructors).constructorCalled
}

assertEquals(listOf("array-2", "array-1", "primary", "string"), result)
}

@Suppress("unused", "UNUSED_PARAMETER")
class MultipleConstructors {
val constructorCalled: String

constructor() {
constructorCalled = "primary"
}

constructor(array: Array<String>?) {
constructorCalled = "array-1"
}

constructor(value: String?) {
constructorCalled = "string"
}

constructor(array1: Array<String>?, array2: BooleanArray?) {
constructorCalled = "array-2"
}
}
}
Loading

0 comments on commit 9e37870

Please sign in to comment.