"some" is a Kotlin test data generation library that creates random instances of data classes, sealed classes/interfaces, collections, and primitive types. It uses a resolver chain pattern where each Resolver handles specific types.
Tech Stack:
- Kotlin (JVM)
- Gradle (Kotlin DSL)
- JUnit 5 (via kotlin-test)
- kotlin-reflect for runtime type introspection
# Run all tests
./gradlew test
# Run a single test class
./gradlew test --tests "*IntResolverTest"
# Run a single test method (use full method name with backticks escaped)
./gradlew test --tests "*IntResolverTest.IntResolver generates int values"
# Run tests in a specific package
./gradlew test --tests "dev.appoutlet.some.resolver.*"
# Run tests with fresh execution (ignore cache)
./gradlew test --rerun-tasksDocumentation pages are under docs/ and the site config is in zensical.toml.
src/main/kotlin/dev/appoutlet/some/
├── config/ # Configuration, strategies, and builder
├── core/ # Core abstractions and resolver chain
├── exception/ # Custom exceptions
└── resolver/ # Type resolvers
- One class per file - Each resolver, exception, and config class has its own file
- File name matches class name -
IntResolver.ktcontainsclass IntResolver - Package structure mirrors functionality - resolvers in
resolver/, config inconfig/
- Resolvers:
{Type}Resolver(e.g.,IntResolver,ListResolver) - Kotlin vs Java types: Prefix with language (e.g.,
KotlinDurationResolver,JavaDurationResolver)
Strategies are registered via SomeConfigBuilder.strategy() (for strategies) and SomeConfigBuilder.factory() (for type factories):
someSetup {
// Strategy registration - overrides generation behavior
strategy(NullableStrategy.NeverNull)
strategy(StringStrategy.Uuid)
strategy(CollectionStrategy(5..10))
// Factory registration - overrides type resolution
factory(String::class) { "fixed-value" }
}Resolvers receive only the strategy they need (nullable, falling back to the strategy default).
Custom factories access strategies through FixtureContext.strategyProvider using the idiomatic get() extension:
factory(MyType::class) {
val strategy = strategyProvider.get<StringStrategy>()
MyType(strategy is StringStrategy.Readable)
}Always use typeOf<T>() for exact type matching, not toString().contains():
// CORRECT
override fun canResolve(type: KType): Boolean = type == typeOf<Int>()
// WRONG - unreliable, causes false positives
override fun canResolve(type: KType): Boolean = type.toString().contains("Int")Each resolver should have 3+ tests:
class MyTypeResolverTest {
@Test
fun `MyTypeResolver generates MyType values`() {
val resolver = MyTypeResolver(MyStrategy(), Random.Default)
val result = resolver.resolve(typeOf<MyType>(), defaultTestChain)
assertIs<MyType>(result)
}
@Test
fun `MyTypeResolver canResolve detects MyType type`() {
val resolver = MyTypeResolver(random = Random.Default)
assertTrue(resolver.canResolve(typeOf<MyType>()))
}
@Test
fun `MyTypeResolver rejects non-MyType types`() {
val resolver = MyTypeResolver(random = Random.Default)
assertFalse(resolver.canResolve(typeOf<String>()))
assertFalse(resolver.canResolve(typeOf<Int>()))
}
}- Use custom exceptions in
dev.appoutlet.some.exceptionpackage - Exception classes extend
Exceptionwith descriptive messages
Order matters - first match wins:
- CustomTypeFactoryResolver (user overrides)
- NullableResolver
- ObjectResolver, EnumResolver, SealedClassResolver, ValueClassResolver
- Primitive resolvers (String, Int, Long, etc.)
- Kotlin native types FIRST (KotlinUuidResolver, KotlinInstantResolver, KotlinDurationResolver)
- Java types SECOND (JavaUuidResolver, JavaInstantResolver, JavaDurationResolver, JavaZonedDateTimeResolver)
- Collection resolvers (List, Set, Map, Array)
- ClassResolver (fallback for classes with constructors)