Skip to content

Commit 9e37870

Browse files
authored
Add constructor strategy (#50)
Fixes #49
1 parent 8defe69 commit 9e37870

15 files changed

+655
-1
lines changed

README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,42 @@ ktype kotlin.String →
395395
Success(5878ec34-c30f-40c7-ad52-c15a39b44ac1)
396396
```
397397

398+
#### constructorStrategy
399+
400+
By default when the library generates an instance of a class it picks a
401+
constructor at random. This can be overridden by setting a constructor
402+
strategy.
403+
404+
```kotlin
405+
val fixture = kotlinFixture {
406+
constructorStrategy(ModestConstructorStrategy)
407+
}
408+
```
409+
410+
The following strategies are built in:
411+
412+
- [RandomConstructorStrategy](fixture/src/main/kotlin/com/appmattus/kotlinfixture/decorator/constructor/RandomConstructorStrategy.kt),
413+
order constructors by random.
414+
415+
- [ModestConstructorStrategy](fixture/src/main/kotlin/com/appmattus/kotlinfixture/decorator/constructor/ModestConstructorStrategy.kt),
416+
order constructors by the most modest constructor first. i.e. fewer
417+
parameters returned first.
418+
419+
- [GreedyConstructorStrategy](fixture/src/main/kotlin/com/appmattus/kotlinfixture/decorator/constructor/GreedyConstructorStrategy.kt),
420+
order constructors by the most greedy constructor first. i.e. greater
421+
parameters returned first.
422+
423+
- [ArrayFavouringConstructorStrategy](fixture/src/main/kotlin/com/appmattus/kotlinfixture/decorator/constructor/ArrayFavouringConstructorStrategy.kt),
424+
order constructors selecting those with the most parameters of
425+
Array<*> before any other.
426+
427+
- [ListFavouringConstructorStrategy](fixture/src/main/kotlin/com/appmattus/kotlinfixture/decorator/constructor/ListFavouringConstructorStrategy.kt),
428+
order constructors selecting those with the most parameters of List<*>
429+
before any other.
430+
431+
It is also possible to define and implement your own constructor
432+
strategy by implementing `ConstructorStrategy` and applying it as above.
433+
398434
## Kotest support
399435

400436
[Kotest](https://github.com/kotest/kotest/) supports
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 2020 Appmattus Limited
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.appmattus.kotlinfixture.decorator.constructor
18+
19+
import com.appmattus.kotlinfixture.Context
20+
import kotlin.reflect.KClass
21+
import kotlin.reflect.KFunction
22+
import kotlin.reflect.full.starProjectedType
23+
24+
/**
25+
* Order constructors, selecting those with the most parameters of Array<*> before any other. Any other constructors
26+
* are returned with the most modest constructors first.
27+
*/
28+
object ArrayFavouringConstructorStrategy : ConstructorStrategy {
29+
30+
@Suppress("EXPERIMENTAL_API_USAGE")
31+
private val arrayTypes = listOf(
32+
Array<Any>::class,
33+
34+
BooleanArray::class,
35+
ByteArray::class,
36+
DoubleArray::class,
37+
FloatArray::class,
38+
IntArray::class,
39+
LongArray::class,
40+
ShortArray::class,
41+
CharArray::class,
42+
UByteArray::class,
43+
UIntArray::class,
44+
ULongArray::class,
45+
UShortArray::class
46+
).map { it.starProjectedType }
47+
48+
override fun constructors(context: Context, obj: KClass<*>): Collection<KFunction<*>> {
49+
return ModestConstructorStrategy.constructors(context, obj).sortedByDescending {
50+
it.parameters.count { parameter ->
51+
(parameter.type.classifier as KClass<*>).starProjectedType in arrayTypes
52+
}
53+
}
54+
}
55+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2020 Appmattus Limited
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.appmattus.kotlinfixture.decorator.constructor
18+
19+
import com.appmattus.kotlinfixture.config.ConfigurationBuilder
20+
21+
/**
22+
* Set the ordering strategy used to try class constructors when generating an instance.
23+
* Default: [RandomConstructorStrategy]
24+
*/
25+
@Suppress("unused")
26+
fun ConfigurationBuilder.constructorStrategy(strategy: ConstructorStrategy) {
27+
strategies[ConstructorStrategy::class] = strategy
28+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright 2020 Appmattus Limited
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.appmattus.kotlinfixture.decorator.constructor
18+
19+
import com.appmattus.kotlinfixture.Context
20+
import kotlin.reflect.KClass
21+
import kotlin.reflect.KFunction
22+
23+
/**
24+
* Strategy used to determine order to try class constructors when generating an instance.
25+
*/
26+
interface ConstructorStrategy {
27+
/**
28+
* Returns [obj] constructors in the order to try when generating an instance.
29+
*/
30+
fun constructors(context: Context, obj: KClass<*>): Collection<KFunction<*>>
31+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright 2020 Appmattus Limited
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.appmattus.kotlinfixture.decorator.constructor
18+
19+
import com.appmattus.kotlinfixture.Context
20+
import kotlin.reflect.KClass
21+
import kotlin.reflect.KFunction
22+
23+
/**
24+
* Order constructors by the most greedy constructor first, i.e. greater parameters returned first. This means that if a
25+
* default constructor exists, it will be the last one returned.
26+
*
27+
* In case of two constructors with an equal number of parameters, the ordering is unspecified.
28+
*/
29+
object GreedyConstructorStrategy : ConstructorStrategy {
30+
override fun constructors(context: Context, obj: KClass<*>): Collection<KFunction<*>> {
31+
return obj.constructors.sortedByDescending { it.parameters.size }
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2020 Appmattus Limited
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.appmattus.kotlinfixture.decorator.constructor
18+
19+
import com.appmattus.kotlinfixture.Context
20+
import kotlin.reflect.KClass
21+
import kotlin.reflect.KFunction
22+
import kotlin.reflect.full.starProjectedType
23+
24+
/**
25+
* Order constructors, selecting those with the most parameters matching List<*> before any other. Any other
26+
* constructors are returned with the most modest constructors first.
27+
*/
28+
object ListFavouringConstructorStrategy : ConstructorStrategy {
29+
override fun constructors(context: Context, obj: KClass<*>): Collection<KFunction<*>> {
30+
return ModestConstructorStrategy.constructors(context, obj).sortedByDescending {
31+
it.parameters.count { parameter ->
32+
(parameter.type.classifier as KClass<*>).starProjectedType == List::class.starProjectedType
33+
}
34+
}
35+
}
36+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright 2020 Appmattus Limited
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.appmattus.kotlinfixture.decorator.constructor
18+
19+
import com.appmattus.kotlinfixture.Context
20+
import kotlin.reflect.KClass
21+
import kotlin.reflect.KFunction
22+
23+
/**
24+
* Order constructors by the most modest constructor first, i.e. fewer parameters returned first. This means that if a
25+
* default constructor exists, it will be the first one returned.
26+
*
27+
* In case of two constructors with an equal number of parameters, the ordering is unspecified.
28+
*/
29+
object ModestConstructorStrategy : ConstructorStrategy {
30+
override fun constructors(context: Context, obj: KClass<*>): Collection<KFunction<*>> {
31+
return obj.constructors.sortedBy { it.parameters.size }
32+
}
33+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright 2020 Appmattus Limited
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.appmattus.kotlinfixture.decorator.constructor
18+
19+
import com.appmattus.kotlinfixture.Context
20+
import kotlin.reflect.KClass
21+
import kotlin.reflect.KFunction
22+
23+
/**
24+
* Order constructors at random.
25+
*/
26+
object RandomConstructorStrategy : ConstructorStrategy {
27+
override fun constructors(context: Context, obj: KClass<*>): Collection<KFunction<*>> {
28+
return obj.constructors.shuffled(context.random)
29+
}
30+
}

fixture/src/main/kotlin/com/appmattus/kotlinfixture/resolver/ClassResolver.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ package com.appmattus.kotlinfixture.resolver
1919
import com.appmattus.kotlinfixture.Context
2020
import com.appmattus.kotlinfixture.Unresolved
2121
import com.appmattus.kotlinfixture.createUnresolved
22+
import com.appmattus.kotlinfixture.decorator.constructor.ConstructorStrategy
23+
import com.appmattus.kotlinfixture.decorator.constructor.RandomConstructorStrategy
24+
import com.appmattus.kotlinfixture.strategyOrDefault
2225
import kotlin.reflect.KClass
2326

2427
internal class ClassResolver : Resolver, PopulateInstance {
@@ -33,7 +36,9 @@ internal class ClassResolver : Resolver, PopulateInstance {
3336
callingClass = obj
3437
)
3538

36-
val results = obj.constructors.shuffled().map { constructor ->
39+
val constructorStrategy = context.strategyOrDefault<ConstructorStrategy>(RandomConstructorStrategy)
40+
41+
val results = constructorStrategy.constructors(context, obj).map { constructor ->
3742
val result = context.resolve(KFunctionRequest(obj, constructor))
3843
if (result !is Unresolved) {
3944
return if (populatePropertiesAndSetters(callContext, result)) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright 2020 Appmattus Limited
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.appmattus.kotlinfixture.decorator.constructor
18+
19+
import com.appmattus.kotlinfixture.ContextImpl
20+
import com.appmattus.kotlinfixture.config.Configuration
21+
import com.nhaarman.mockitokotlin2.doReturn
22+
import com.nhaarman.mockitokotlin2.mock
23+
import kotlin.reflect.KClass
24+
import kotlin.test.Test
25+
import kotlin.test.assertEquals
26+
27+
class ArrayFavouringConstructorStrategyTest {
28+
29+
@Test
30+
fun `Order constructors with greatest array parameter count first followed by remaining modest first`() {
31+
val context = ContextImpl(Configuration())
32+
33+
val shuffledConstructors = mock<KClass<MultipleConstructors>> {
34+
on { constructors } doReturn MultipleConstructors::class.constructors.shuffled()
35+
}
36+
37+
val result = ArrayFavouringConstructorStrategy.constructors(context, shuffledConstructors).map {
38+
val emptyParameters = List<Any?>(it.parameters.size) { null }
39+
(it.call(*emptyParameters.toTypedArray()) as MultipleConstructors).constructorCalled
40+
}
41+
42+
assertEquals(listOf("array-2", "array-1", "primary", "string"), result)
43+
}
44+
45+
@Suppress("unused", "UNUSED_PARAMETER")
46+
class MultipleConstructors {
47+
val constructorCalled: String
48+
49+
constructor() {
50+
constructorCalled = "primary"
51+
}
52+
53+
constructor(array: Array<String>?) {
54+
constructorCalled = "array-1"
55+
}
56+
57+
constructor(value: String?) {
58+
constructorCalled = "string"
59+
}
60+
61+
constructor(array1: Array<String>?, array2: BooleanArray?) {
62+
constructorCalled = "array-2"
63+
}
64+
}
65+
}

0 commit comments

Comments
 (0)