diff --git a/README.md b/README.md index 8cad836e..e66b5b6d 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,7 @@ val futureDate = fixture { } ``` -### property +#### property Used to override constructor parameters or mutable properties when generating instances of generic classes. @@ -216,6 +216,55 @@ val aStaticValue = fixture() { } ``` +### recursionStrategy + +When recursion is detected the library will, by default, throw a +`FixtureException` with the details of the circular reference. This strategy +can be changed to instead return `null` for the reference, however, if this +results in an invalid object an exception will still be thrown as the object +requested couldn't be resolved. + +```kotlin +val fixture = kotlinFixture { + recursionStrategy(NullRecursionStrategy) +} + +// You can also override at instance creation + +fixture { + recursionStrategy(NullRecursionStrategy) +} +``` + +It is also possible to define and implement your own recursion strategy by +implementing `RecursionStrategy` and applying it as above. + +### loggingStrategy + +A basic logger can be applied using the built in `SysOutLoggingStrategy`. It is +also possible to define and implement your own logging strategy by implementing +`LoggingStrategy` and applying it as below. + +```kotlin +val fixture = kotlinFixture { + loggingStrategy(SysOutLoggingStrategy) +} + +fixture() { + // You can also override at instance creation + loggingStrategy(SysOutLoggingStrategy) +} +``` + +This outputs: + +```text +ktype kotlin.String → + class kotlin.String → + Success(5878ec34-c30f-40c7-ad52-c15a39b44ac1) + Success(5878ec34-c30f-40c7-ad52-c15a39b44ac1) +``` + ## Contributing Please fork this repository and contribute back using diff --git a/fixture/build.gradle.kts b/fixture/build.gradle.kts index 9b113600..64ce75ff 100644 --- a/fixture/build.gradle.kts +++ b/fixture/build.gradle.kts @@ -50,3 +50,5 @@ tasks.whenTaskAdded { from(sourceSets.main.get().allSource) } } + +tasks.getByName("check").finalizedBy(rootProject.tasks.getByName("detekt")) diff --git a/fixture/src/main/kotlin/com/appmattus/kotlinfixture/Context.kt b/fixture/src/main/kotlin/com/appmattus/kotlinfixture/Context.kt index 48c5cc10..7e442ace 100644 --- a/fixture/src/main/kotlin/com/appmattus/kotlinfixture/Context.kt +++ b/fixture/src/main/kotlin/com/appmattus/kotlinfixture/Context.kt @@ -29,3 +29,5 @@ interface Context { fun resolve(obj: Any) = resolver.resolve(this, obj) } + +inline fun Context.strategyOrDefault(default: T): T = configuration.strategies[T::class] as? T ?: default diff --git a/fixture/src/main/kotlin/com/appmattus/kotlinfixture/KotlinFixture.kt b/fixture/src/main/kotlin/com/appmattus/kotlinfixture/KotlinFixture.kt index 7469ccff..351aa3f8 100644 --- a/fixture/src/main/kotlin/com/appmattus/kotlinfixture/KotlinFixture.kt +++ b/fixture/src/main/kotlin/com/appmattus/kotlinfixture/KotlinFixture.kt @@ -14,14 +14,12 @@ * limitations under the License. */ +@file:Suppress("MatchingDeclarationName") + package com.appmattus.kotlinfixture import com.appmattus.kotlinfixture.config.Configuration import com.appmattus.kotlinfixture.config.ConfigurationBuilder -import com.appmattus.kotlinfixture.decorator.logging.LoggingDecorator -import com.appmattus.kotlinfixture.decorator.logging.SysOutLoggingStrategy -import com.appmattus.kotlinfixture.decorator.recursion.NullRecursionStrategy -import com.appmattus.kotlinfixture.decorator.recursion.RecursionDecorator import kotlin.reflect.KType class Fixture(val fixtureConfiguration: Configuration) { @@ -50,34 +48,3 @@ class Fixture(val fixtureConfiguration: Configuration) { fun kotlinFixture(init: ConfigurationBuilder.() -> Unit = {}) = Fixture(ConfigurationBuilder().apply(init).build()) - -fun main() { - - val fixture = kotlinFixture() - - fixture> { - decorators.add(LoggingDecorator(SysOutLoggingStrategy())) - } - - println(fixture { - decorators.removeIf { it is RecursionDecorator } - decorators.add(RecursionDecorator(NullRecursionStrategy())) - decorators.add(LoggingDecorator(SysOutLoggingStrategy())) - }) -} - -class A { - lateinit var b: B - - override fun toString(): String { - return "A[${if (::b.isInitialized) b.toString() else "uninit"}]" - } -} - -class B { - lateinit var a: A - - override fun toString(): String { - return "B[${if (::a.isInitialized) a.toString() else "uninit"}]" - } -} diff --git a/fixture/src/main/kotlin/com/appmattus/kotlinfixture/ToDo.kt b/fixture/src/main/kotlin/com/appmattus/kotlinfixture/ToDo.kt index 0da4e693..c77a3d33 100644 --- a/fixture/src/main/kotlin/com/appmattus/kotlinfixture/ToDo.kt +++ b/fixture/src/main/kotlin/com/appmattus/kotlinfixture/ToDo.kt @@ -2,13 +2,6 @@ package com.appmattus.kotlinfixture @Suppress("unused") private object ToDo { - // TODO SynchronousQueue - adding elements to this makes little sense - // TODO ArrayBlockingQueue - needs a fixed capacity - - // TODO handle & detect circular dependencies. See circularDependencyBehaviour - - // TODO handle unresolvable, throw exception or return null. See noResolutionBehaviour - // TODO Check out competition projects // - https://github.com/FlexTradeUKLtd/kfixture // - https://github.com/marcellogalhardo/kotlin-fixture/tree/develop diff --git a/fixture/src/main/kotlin/com/appmattus/kotlinfixture/config/Configuration.kt b/fixture/src/main/kotlin/com/appmattus/kotlinfixture/config/Configuration.kt index c66634f1..a8f32fcd 100644 --- a/fixture/src/main/kotlin/com/appmattus/kotlinfixture/config/Configuration.kt +++ b/fixture/src/main/kotlin/com/appmattus/kotlinfixture/config/Configuration.kt @@ -17,8 +17,8 @@ package com.appmattus.kotlinfixture.config import com.appmattus.kotlinfixture.decorator.Decorator +import com.appmattus.kotlinfixture.decorator.logging.LoggingDecorator import com.appmattus.kotlinfixture.decorator.recursion.RecursionDecorator -import com.appmattus.kotlinfixture.decorator.recursion.ThrowingRecursionStrategy import com.appmattus.kotlinfixture.resolver.AbstractClassResolver import com.appmattus.kotlinfixture.resolver.ArrayResolver import com.appmattus.kotlinfixture.resolver.BigDecimalResolver @@ -62,7 +62,8 @@ data class Configuration( val subTypes: Map, KClass<*>> = emptyMap, KClass<*>>().toUnmodifiableMap(), val random: Random = defaultRandom, val decorators: List = defaultDecorators.toUnmodifiableList(), - val resolvers: List = defaultResolvers.toUnmodifiableList() + val resolvers: List = defaultResolvers.toUnmodifiableList(), + val strategies: Map, Any> = emptyMap, Any>().toUnmodifiableMap() ) { private companion object { @@ -70,7 +71,7 @@ data class Configuration( private val defaultRandom = Random - private val defaultDecorators = listOf(RecursionDecorator(ThrowingRecursionStrategy())) + private val defaultDecorators = listOf(RecursionDecorator(), LoggingDecorator()) private val defaultResolvers = listOf( CharResolver(), diff --git a/fixture/src/main/kotlin/com/appmattus/kotlinfixture/config/ConfigurationBuilder.kt b/fixture/src/main/kotlin/com/appmattus/kotlinfixture/config/ConfigurationBuilder.kt index 2646afb8..f2714d3a 100644 --- a/fixture/src/main/kotlin/com/appmattus/kotlinfixture/config/ConfigurationBuilder.kt +++ b/fixture/src/main/kotlin/com/appmattus/kotlinfixture/config/ConfigurationBuilder.kt @@ -41,6 +41,8 @@ class ConfigurationBuilder(configuration: Configuration = Configuration()) { private val instances: MutableMap.() -> Any?> = configuration.instances.toMutableMap() private val subTypes: MutableMap, KClass<*>> = configuration.subTypes.toMutableMap() + internal val strategies: MutableMap, Any> = configuration.strategies.toMutableMap() + @Suppress("UNCHECKED_CAST") inline fun instance(noinline generator: Generator.() -> T) = instance(typeOf(), generator as Generator.() -> Any?) @@ -94,6 +96,7 @@ class ConfigurationBuilder(configuration: Configuration = Configuration()) { subTypes = subTypes.toUnmodifiableMap(), random = random, decorators = decorators.toUnmodifiableList(), - resolvers = resolvers.toUnmodifiableList() + resolvers = resolvers.toUnmodifiableList(), + strategies = strategies.toUnmodifiableMap() ) } diff --git a/fixture/src/main/kotlin/com/appmattus/kotlinfixture/decorator/logging/LoggingConfiguration.kt b/fixture/src/main/kotlin/com/appmattus/kotlinfixture/decorator/logging/LoggingConfiguration.kt new file mode 100644 index 00000000..b541a5a8 --- /dev/null +++ b/fixture/src/main/kotlin/com/appmattus/kotlinfixture/decorator/logging/LoggingConfiguration.kt @@ -0,0 +1,8 @@ +package com.appmattus.kotlinfixture.decorator.logging + +import com.appmattus.kotlinfixture.config.ConfigurationBuilder + +@Suppress("unused") +fun ConfigurationBuilder.loggingStrategy(strategy: LoggingStrategy) { + strategies[LoggingStrategy::class] = strategy +} diff --git a/fixture/src/main/kotlin/com/appmattus/kotlinfixture/decorator/logging/LoggingDecorator.kt b/fixture/src/main/kotlin/com/appmattus/kotlinfixture/decorator/logging/LoggingDecorator.kt index 3acf8380..b3d22593 100644 --- a/fixture/src/main/kotlin/com/appmattus/kotlinfixture/decorator/logging/LoggingDecorator.kt +++ b/fixture/src/main/kotlin/com/appmattus/kotlinfixture/decorator/logging/LoggingDecorator.kt @@ -19,17 +19,19 @@ package com.appmattus.kotlinfixture.decorator.logging import com.appmattus.kotlinfixture.Context import com.appmattus.kotlinfixture.decorator.Decorator import com.appmattus.kotlinfixture.resolver.Resolver +import com.appmattus.kotlinfixture.strategyOrDefault -class LoggingDecorator(private val strategy: LoggingStrategy) : Decorator { +internal class LoggingDecorator : Decorator { - override fun decorate(resolver: Resolver): Resolver = LoggingResolver(resolver, strategy) + override fun decorate(resolver: Resolver): Resolver = LoggingResolver(resolver) private class LoggingResolver( - private val resolver: Resolver, - private val strategy: LoggingStrategy + private val resolver: Resolver ) : Resolver { override fun resolve(context: Context, obj: Any): Any? { + val strategy = context.strategyOrDefault(NoLoggingStrategy) + strategy.request(obj) try { diff --git a/fixture/src/main/kotlin/com/appmattus/kotlinfixture/decorator/logging/NoLoggingStrategy.kt b/fixture/src/main/kotlin/com/appmattus/kotlinfixture/decorator/logging/NoLoggingStrategy.kt new file mode 100644 index 00000000..d4898458 --- /dev/null +++ b/fixture/src/main/kotlin/com/appmattus/kotlinfixture/decorator/logging/NoLoggingStrategy.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2019 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.logging + +object NoLoggingStrategy : LoggingStrategy { + override fun request(obj: Any) = Unit + + override fun response(obj: Any, result: Result) = Unit +} diff --git a/fixture/src/main/kotlin/com/appmattus/kotlinfixture/decorator/logging/SysOutLoggingStrategy.kt b/fixture/src/main/kotlin/com/appmattus/kotlinfixture/decorator/logging/SysOutLoggingStrategy.kt index 45a38747..a49f4fd1 100644 --- a/fixture/src/main/kotlin/com/appmattus/kotlinfixture/decorator/logging/SysOutLoggingStrategy.kt +++ b/fixture/src/main/kotlin/com/appmattus/kotlinfixture/decorator/logging/SysOutLoggingStrategy.kt @@ -17,15 +17,21 @@ package com.appmattus.kotlinfixture.decorator.logging import java.util.Stack +import kotlin.reflect.KType -class SysOutLoggingStrategy : LoggingStrategy { +object SysOutLoggingStrategy : LoggingStrategy { private val stack = Stack() override fun request(obj: Any) { stack.push(obj) val indent = " ".repeat(stack.size - 1) - println("$indent$obj → ") + + val prefix = when (obj) { + is KType -> "ktype " + else -> "" + } + println("$indent$prefix$obj → ") } override fun response(obj: Any, result: Result) { diff --git a/fixture/src/main/kotlin/com/appmattus/kotlinfixture/decorator/recursion/NullRecursionStrategy.kt b/fixture/src/main/kotlin/com/appmattus/kotlinfixture/decorator/recursion/NullRecursionStrategy.kt index de3a808a..e20e512c 100644 --- a/fixture/src/main/kotlin/com/appmattus/kotlinfixture/decorator/recursion/NullRecursionStrategy.kt +++ b/fixture/src/main/kotlin/com/appmattus/kotlinfixture/decorator/recursion/NullRecursionStrategy.kt @@ -18,7 +18,11 @@ package com.appmattus.kotlinfixture.decorator.recursion import kotlin.reflect.KType -class NullRecursionStrategy : RecursionStrategy { +object NullRecursionStrategy : RecursionStrategy { - override fun handleRecursion(type: KType, stack: Collection) = null + override fun handleRecursion(type: KType, stack: Collection): Any? { + check(stack.isNotEmpty()) { "Stack must be populated" } + + return null + } } diff --git a/fixture/src/main/kotlin/com/appmattus/kotlinfixture/decorator/recursion/RecursionConfiguration.kt b/fixture/src/main/kotlin/com/appmattus/kotlinfixture/decorator/recursion/RecursionConfiguration.kt new file mode 100644 index 00000000..10e4d4ed --- /dev/null +++ b/fixture/src/main/kotlin/com/appmattus/kotlinfixture/decorator/recursion/RecursionConfiguration.kt @@ -0,0 +1,8 @@ +package com.appmattus.kotlinfixture.decorator.recursion + +import com.appmattus.kotlinfixture.config.ConfigurationBuilder + +@Suppress("unused") +fun ConfigurationBuilder.recursionStrategy(strategy: RecursionStrategy) { + strategies[RecursionStrategy::class] = strategy +} diff --git a/fixture/src/main/kotlin/com/appmattus/kotlinfixture/decorator/recursion/RecursionDecorator.kt b/fixture/src/main/kotlin/com/appmattus/kotlinfixture/decorator/recursion/RecursionDecorator.kt index fa9a18ef..a926e016 100644 --- a/fixture/src/main/kotlin/com/appmattus/kotlinfixture/decorator/recursion/RecursionDecorator.kt +++ b/fixture/src/main/kotlin/com/appmattus/kotlinfixture/decorator/recursion/RecursionDecorator.kt @@ -19,27 +19,25 @@ package com.appmattus.kotlinfixture.decorator.recursion import com.appmattus.kotlinfixture.Context import com.appmattus.kotlinfixture.decorator.Decorator import com.appmattus.kotlinfixture.resolver.Resolver +import com.appmattus.kotlinfixture.strategyOrDefault import java.util.Stack import kotlin.reflect.KType -class RecursionDecorator( - private val strategy: RecursionStrategy -) : Decorator { +internal class RecursionDecorator : Decorator { - override fun decorate(resolver: Resolver): Resolver = RecursionResolver(resolver, strategy) + override fun decorate(resolver: Resolver): Resolver = RecursionResolver(resolver) - private class RecursionResolver( - private val resolver: Resolver, - private val recursionStrategy: RecursionStrategy - ) : Resolver { + private class RecursionResolver(private val resolver: Resolver) : Resolver { private val stack = Stack() @Suppress("ReturnCount") override fun resolve(context: Context, obj: Any): Any? { + val strategy = context.strategyOrDefault(ThrowingRecursionStrategy) + if (obj is KType) { if (stack.contains(obj)) { - return recursionStrategy.handleRecursion(obj, stack) + return strategy.handleRecursion(obj, stack.toList()) } stack.push(obj) diff --git a/fixture/src/main/kotlin/com/appmattus/kotlinfixture/decorator/recursion/ThrowingRecursionStrategy.kt b/fixture/src/main/kotlin/com/appmattus/kotlinfixture/decorator/recursion/ThrowingRecursionStrategy.kt index 9fdee917..2672e3a4 100644 --- a/fixture/src/main/kotlin/com/appmattus/kotlinfixture/decorator/recursion/ThrowingRecursionStrategy.kt +++ b/fixture/src/main/kotlin/com/appmattus/kotlinfixture/decorator/recursion/ThrowingRecursionStrategy.kt @@ -19,9 +19,11 @@ package com.appmattus.kotlinfixture.decorator.recursion import com.appmattus.kotlinfixture.FixtureException import kotlin.reflect.KType -class ThrowingRecursionStrategy : RecursionStrategy { +object ThrowingRecursionStrategy : RecursionStrategy { override fun handleRecursion(type: KType, stack: Collection): Any? { + check(stack.isNotEmpty()) { "Stack must be populated" } + val errorMessage = "Unable to create ${stack.first()} with circular reference: ${stack.toStackString(type)}" throw FixtureException(errorMessage) } diff --git a/fixture/src/test/kotlin/com/appmattus/kotlinfixture/FixtureTest.kt b/fixture/src/test/kotlin/com/appmattus/kotlinfixture/FixtureTest.kt index b289ec1f..1171082b 100644 --- a/fixture/src/test/kotlin/com/appmattus/kotlinfixture/FixtureTest.kt +++ b/fixture/src/test/kotlin/com/appmattus/kotlinfixture/FixtureTest.kt @@ -36,7 +36,7 @@ class FixtureTest { val missingResolvers = Classes.classGraph.getClassInfo(Resolver::class.java.name).classesImplementing .map { it.simpleName } - .filterNot { it == "TestResolver" || it == "CompositeResolver" } + .filterNot { it.endsWith("TestResolver") || it == "CompositeResolver" } .sorted() .toMutableList().apply { removeAll(actualResolvers) diff --git a/fixture/src/test/kotlin/com/appmattus/kotlinfixture/decorator/logging/LoggingDecoratorTest.kt b/fixture/src/test/kotlin/com/appmattus/kotlinfixture/decorator/logging/LoggingDecoratorTest.kt new file mode 100644 index 00000000..292e6285 --- /dev/null +++ b/fixture/src/test/kotlin/com/appmattus/kotlinfixture/decorator/logging/LoggingDecoratorTest.kt @@ -0,0 +1,62 @@ +package com.appmattus.kotlinfixture.decorator.logging + +import com.appmattus.kotlinfixture.Context +import com.appmattus.kotlinfixture.TestContext +import com.appmattus.kotlinfixture.config.ConfigurationBuilder +import com.appmattus.kotlinfixture.resolver.Resolver +import com.appmattus.kotlinfixture.typeOf +import com.nhaarman.mockitokotlin2.argumentCaptor +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.verify +import org.mockito.internal.verification.Times +import kotlin.reflect.KType +import kotlin.test.Test +import kotlin.test.assertEquals + +class LoggingDecoratorTest { + + private val mockLoggingStrategy = mock() + + private val config = ConfigurationBuilder().apply { + loggingStrategy(mockLoggingStrategy) + }.build() + + @Test + fun `logs each request`() { + val resolver = TestResolver(listOf(typeOf(), typeOf())) + + val decoratedResolver = LoggingDecorator().decorate(resolver) + decoratedResolver.resolve(TestContext(config, decoratedResolver), typeOf()) + + argumentCaptor { + verify(mockLoggingStrategy, Times(3)).request(capture()) + + assertEquals(listOf(typeOf(), typeOf(), typeOf()), allValues) + } + } + + @Test + fun `logs each response`() { + val resolver = TestResolver(listOf(typeOf(), typeOf())) + + val decoratedResolver = LoggingDecorator().decorate(resolver) + decoratedResolver.resolve(TestContext(config, decoratedResolver), typeOf()) + + verify(mockLoggingStrategy).response(typeOf(), Result.success(typeOf())) + verify(mockLoggingStrategy).response(typeOf(), Result.success(typeOf())) + verify(mockLoggingStrategy).response(typeOf(), Result.success(typeOf())) + } + + class TestResolver(list: List) : Resolver { + + private val objects = list.iterator() + + override fun resolve(context: Context, obj: Any): Any? { + return if (objects.hasNext()) { + context.resolve(objects.next()) + } else { + obj + } + } + } +} diff --git a/fixture/src/test/kotlin/com/appmattus/kotlinfixture/decorator/logging/NoLoggingStrategyTest.kt b/fixture/src/test/kotlin/com/appmattus/kotlinfixture/decorator/logging/NoLoggingStrategyTest.kt new file mode 100644 index 00000000..258db652 --- /dev/null +++ b/fixture/src/test/kotlin/com/appmattus/kotlinfixture/decorator/logging/NoLoggingStrategyTest.kt @@ -0,0 +1,30 @@ +package com.appmattus.kotlinfixture.decorator.logging + +import java.util.UUID +import kotlin.test.Test +import kotlin.test.assertFalse + +class NoLoggingStrategyTest { + + @Test + fun `values and responses are not logged`() { + val value1 = UUID.randomUUID().toString() + val value2 = UUID.randomUUID().toString() + val response1 = UUID.randomUUID().toString() + val response2 = UUID.randomUUID().toString() + + val output = captureSysOut { + with(NoLoggingStrategy) { + request(value1) + request(value2) + response(value2, Result.success(response2)) + response(value1, Result.success(response1)) + } + } + + assertFalse { output.contains(value1) } + assertFalse { output.contains(value2) } + assertFalse { output.contains(response1) } + assertFalse { output.contains(response2) } + } +} diff --git a/fixture/src/test/kotlin/com/appmattus/kotlinfixture/decorator/logging/SysOutCaptor.kt b/fixture/src/test/kotlin/com/appmattus/kotlinfixture/decorator/logging/SysOutCaptor.kt new file mode 100644 index 00000000..1ee5dc09 --- /dev/null +++ b/fixture/src/test/kotlin/com/appmattus/kotlinfixture/decorator/logging/SysOutCaptor.kt @@ -0,0 +1,20 @@ +package com.appmattus.kotlinfixture.decorator.logging + +import java.io.ByteArrayOutputStream +import java.io.PrintStream + +fun captureSysOut(block: () -> Unit): String { + ByteArrayOutputStream().use { + val ps = PrintStream(it) + val old = System.out + + System.setOut(ps) + + block() + + System.out.flush() + System.setOut(old) + + return it.toString() + } +} diff --git a/fixture/src/test/kotlin/com/appmattus/kotlinfixture/decorator/logging/SysOutLoggingStrategyTest.kt b/fixture/src/test/kotlin/com/appmattus/kotlinfixture/decorator/logging/SysOutLoggingStrategyTest.kt new file mode 100644 index 00000000..42c7c22e --- /dev/null +++ b/fixture/src/test/kotlin/com/appmattus/kotlinfixture/decorator/logging/SysOutLoggingStrategyTest.kt @@ -0,0 +1,107 @@ +package com.appmattus.kotlinfixture.decorator.logging + +import java.util.UUID +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class SysOutLoggingStrategyTest { + @Test + fun `single request logs value`() { + val value = UUID.randomUUID().toString() + + val output = captureSysOut { + SysOutLoggingStrategy.request(value) + } + + assertTrue { + output.contains(value) + } + } + + @Test + fun `multiple requests log both values`() { + val value1 = UUID.randomUUID().toString() + val value2 = UUID.randomUUID().toString() + + val output = captureSysOut { + SysOutLoggingStrategy.request(value1) + SysOutLoggingStrategy.request(value2) + } + + assertTrue { output.contains(value1) } + assertTrue { output.contains(value2) } + } + + @Test + fun `first nested request is indented`() { + val value1 = UUID.randomUUID().toString() + val value2 = UUID.randomUUID().toString() + + val output = captureSysOut { + SysOutLoggingStrategy.request(value1) + SysOutLoggingStrategy.request(value2) + } + + val indent1 = output.lines().first { it.contains(value1) }.indexOf(value1) + val indent2 = output.lines().first { it.contains(value2) }.indexOf(value2) + + assertEquals(indent1 + 4, indent2) + } + + @Test + fun `nested requests are indented`() { + val value1 = UUID.randomUUID().toString() + val value2 = UUID.randomUUID().toString() + val value3 = UUID.randomUUID().toString() + + val output = captureSysOut { + SysOutLoggingStrategy.request(value1) + SysOutLoggingStrategy.request(value2) + SysOutLoggingStrategy.response(value2, Result.success(Unit)) + SysOutLoggingStrategy.request(value3) + } + + val indent1 = output.lines().first { it.contains(value1) }.indexOf(value1) + val indent3 = output.lines().first { it.contains(value3) }.indexOf(value3) + + assertEquals(indent1 + 4, indent3) + } + + @Test + fun `single request logs response`() { + val value = UUID.randomUUID().toString() + val response = UUID.randomUUID().toString() + + val output = captureSysOut { + SysOutLoggingStrategy.request(value) + SysOutLoggingStrategy.response(value, Result.success(response)) + } + + assertTrue { + output.contains("Success($response)") + } + } + + @Test + fun `nested responses are indented`() { + val value1 = UUID.randomUUID().toString() + val value2 = UUID.randomUUID().toString() + val response1 = UUID.randomUUID().toString() + val response2 = UUID.randomUUID().toString() + + val output = captureSysOut { + with(SysOutLoggingStrategy) { + request(value1) + request(value2) + response(value2, Result.success(response2)) + response(value1, Result.success(response1)) + } + } + + val indent1 = output.lines().first { it.contains(response1) }.indexOf(response1) + val indent2 = output.lines().first { it.contains(response2) }.indexOf(response2) + + assertEquals(indent1 + 4, indent2) + } +} diff --git a/fixture/src/test/kotlin/com/appmattus/kotlinfixture/decorator/recursion/NullRecursionStrategyTest.kt b/fixture/src/test/kotlin/com/appmattus/kotlinfixture/decorator/recursion/NullRecursionStrategyTest.kt new file mode 100644 index 00000000..46d0b9e7 --- /dev/null +++ b/fixture/src/test/kotlin/com/appmattus/kotlinfixture/decorator/recursion/NullRecursionStrategyTest.kt @@ -0,0 +1,20 @@ +package com.appmattus.kotlinfixture.decorator.recursion + +import com.appmattus.kotlinfixture.typeOf +import kotlin.test.Test +import kotlin.test.assertFailsWith +import kotlin.test.assertNull + +class NullRecursionStrategyTest { + @Test + fun `throws illegal state exception when stack is empty`() { + assertFailsWith { + NullRecursionStrategy.handleRecursion(typeOf(), emptyList()) + } + } + + @Test + fun `returns null when stack is populated`() { + assertNull(NullRecursionStrategy.handleRecursion(typeOf(), listOf(typeOf(), typeOf()))) + } +} diff --git a/fixture/src/test/kotlin/com/appmattus/kotlinfixture/decorator/recursion/RecursionDecoratorTest.kt b/fixture/src/test/kotlin/com/appmattus/kotlinfixture/decorator/recursion/RecursionDecoratorTest.kt new file mode 100644 index 00000000..b442f371 --- /dev/null +++ b/fixture/src/test/kotlin/com/appmattus/kotlinfixture/decorator/recursion/RecursionDecoratorTest.kt @@ -0,0 +1,55 @@ +package com.appmattus.kotlinfixture.decorator.recursion + +import com.appmattus.kotlinfixture.Context +import com.appmattus.kotlinfixture.TestContext +import com.appmattus.kotlinfixture.config.ConfigurationBuilder +import com.appmattus.kotlinfixture.resolver.Resolver +import com.appmattus.kotlinfixture.typeOf +import com.nhaarman.mockitokotlin2.any +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.verify +import org.mockito.internal.verification.NoMoreInteractions +import kotlin.reflect.KType +import kotlin.test.Test + +class RecursionDecoratorTest { + + private val mockRecursionStrategy = mock() + + private val config = ConfigurationBuilder().apply { + recursionStrategy(mockRecursionStrategy) + }.build() + + @Test + fun `calls resolver when same ktype recursively requested again`() { + val resolver = TestResolver(listOf(typeOf(), typeOf())) + + val decoratedResolver = RecursionDecorator().decorate(resolver) + decoratedResolver.resolve(TestContext(config, decoratedResolver), typeOf()) + + verify(mockRecursionStrategy).handleRecursion(typeOf(), listOf(typeOf(), typeOf())) + } + + @Test + fun `does not call resolver when all different ktypes recursively requested`() { + val resolver = TestResolver(listOf(typeOf(), typeOf(), typeOf())) + + val decoratedResolver = RecursionDecorator().decorate(resolver) + decoratedResolver.resolve(TestContext(config, decoratedResolver), typeOf()) + + verify(mockRecursionStrategy, NoMoreInteractions()).handleRecursion(any(), any()) + } + + class TestResolver(list: List) : Resolver { + + private val objects = list.iterator() + + override fun resolve(context: Context, obj: Any): Any? { + return if (objects.hasNext()) { + context.resolve(objects.next()) + } else { + obj + } + } + } +} diff --git a/fixture/src/test/kotlin/com/appmattus/kotlinfixture/decorator/recursion/ThrowingRecursionStrategyTest.kt b/fixture/src/test/kotlin/com/appmattus/kotlinfixture/decorator/recursion/ThrowingRecursionStrategyTest.kt new file mode 100644 index 00000000..2c04ac75 --- /dev/null +++ b/fixture/src/test/kotlin/com/appmattus/kotlinfixture/decorator/recursion/ThrowingRecursionStrategyTest.kt @@ -0,0 +1,23 @@ +package com.appmattus.kotlinfixture.decorator.recursion + +import com.appmattus.kotlinfixture.FixtureException +import com.appmattus.kotlinfixture.typeOf +import kotlin.test.Test +import kotlin.test.assertFailsWith + +class ThrowingRecursionStrategyTest { + + @Test + fun `throws illegal state exception when stack is empty`() { + assertFailsWith { + ThrowingRecursionStrategy.handleRecursion(typeOf(), emptyList()) + } + } + + @Test + fun `throws expected exception when stack is populated`() { + assertFailsWith { + ThrowingRecursionStrategy.handleRecursion(typeOf(), listOf(typeOf(), typeOf())) + } + } +} diff --git a/fixture/src/test/kotlin/com/appmattus/kotlinfixture/resolver/CalendarResolverTest.kt b/fixture/src/test/kotlin/com/appmattus/kotlinfixture/resolver/CalendarResolverTest.kt index 081794e2..f483029d 100644 --- a/fixture/src/test/kotlin/com/appmattus/kotlinfixture/resolver/CalendarResolverTest.kt +++ b/fixture/src/test/kotlin/com/appmattus/kotlinfixture/resolver/CalendarResolverTest.kt @@ -22,8 +22,6 @@ import com.appmattus.kotlinfixture.assertIsRandom import com.appmattus.kotlinfixture.config.Configuration import com.appmattus.kotlinfixture.config.Generator import com.appmattus.kotlinfixture.config.before -import com.appmattus.kotlinfixture.decorator.logging.LoggingDecorator -import com.appmattus.kotlinfixture.decorator.logging.SysOutLoggingStrategy import com.appmattus.kotlinfixture.typeOf import java.util.Calendar import java.util.Date @@ -42,8 +40,7 @@ class CalendarResolverTest { @Suppress("UNCHECKED_CAST") private val context = TestContext( Configuration( - instances = mapOf(typeOf() to beforeNowGenerator as Generator.() -> Any?), - decorators = listOf(LoggingDecorator(SysOutLoggingStrategy())) + instances = mapOf(typeOf() to beforeNowGenerator as Generator.() -> Any?) ), CompositeResolver(CalendarResolver(), InstanceResolver()) )