Skip to content

Commit

Permalink
feat(core): More idiomatic Kotlin
Browse files Browse the repository at this point in the history
Implement using more idiomatic Kotlin; also: move to JUnit 5

BREAKING CHANGE: use Kotlin lambda instead of Runnable, Function

Use Kotlin lambda instead of Runnable for Prop.addCallback, and instead of Function for PropFactory.parse.
  • Loading branch information
erwinw committed Jan 21, 2022
1 parent f506e74 commit d8d1c63
Show file tree
Hide file tree
Showing 18 changed files with 233 additions and 238 deletions.
20 changes: 18 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ allprojects {
}

subprojects {
ext {
junitVersion = "5.8.2"
kotlinGuiceVersion = "1.5.0"
kotlinReflectVersion = "1.6.10"
mockkVersion = "1.12.2"
}

apply plugin: 'java-library'
apply plugin: "kotlin"
apply plugin: 'maven-publish'
Expand All @@ -50,11 +57,16 @@ subprojects {

dependencies {
implementation 'org.slf4j:slf4j-api:1.7.33'
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinReflectVersion"
implementation "dev.misfitlabs.kotlinguice4:kotlin-guice:$kotlinGuiceVersion"

testImplementation 'junit:junit:4.13.2'
testImplementation 'org.mockito:mockito-core:4.2.0'
testImplementation 'com.google.truth:truth:1.1.3'
testImplementation 'ch.qos.logback:logback-classic:1.2.10'
testImplementation "org.junit.jupiter:junit-jupiter:$junitVersion"
testImplementation "io.mockk:mockk:$mockkVersion"

testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"

}

task sourcesJar(type: Jar) {
Expand All @@ -78,6 +90,10 @@ subprojects {
mavenCentral()
}

test {
useJUnitPlatform()
}

publishing {
publications {
mavenJava(MavenPublication) {
Expand Down
151 changes: 82 additions & 69 deletions prop-all/src/test/kotlin/io/pleo/prop/PropTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.google.inject.Injector
import com.google.inject.Module
import com.google.inject.PrivateModule
import com.google.inject.assistedinject.FactoryModuleBuilder
import io.mockk.mockk
import io.pleo.prop.archaius.ArchaiusPropFactory
import io.pleo.prop.guice.AutoPropModule
import io.pleo.prop.guice.internal.FailedToCreatePropException
Expand All @@ -31,13 +32,13 @@ import io.pleo.prop.objects.NullValue
import io.pleo.prop.objects.SamePropertyAsComplexObjects
import io.pleo.prop.objects.UnnamedProp
import io.pleo.prop.objects.UsesTwiceSameProp
import org.junit.Test
import org.mockito.Mockito
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import javax.sql.DataSource

class PropTest {
@Test
fun can_read_complex_properties() {
fun `can read complex properties`() {
val injector = createInjector(
Module { binder: Binder ->
binder.bind(ComplexObjects::class.java)
Expand All @@ -61,8 +62,8 @@ class PropTest {
}

@Test
fun can_bind_non_prop_objects() {
val dataSource = Mockito.mock(DataSource::class.java)
fun `can bind non prop objects`() {
val dataSource = mockk<DataSource>()
val injector = createInjector(
Module { binder: Binder ->
binder.bind(DataSource::class.java).toInstance(dataSource)
Expand All @@ -75,21 +76,23 @@ class PropTest {
assertThat(actual.dataSource).isEqualTo(dataSource)
}

@Test(expected = FailedToCreatePropException::class)
fun throws_on_null_values() {
val injector = createInjector(
Module { binder: Binder ->
binder.bind(
NullValue::class.java
)
}
)
@Test
fun `throws on null values`() {
assertThrows<FailedToCreatePropException> {
val injector = createInjector(
Module { binder: Binder ->
binder.bind(
NullValue::class.java
)
}
)

injector.getInstance(NullValue::class.java)
injector.getInstance(NullValue::class.java)
}
}

@Test
fun uses_default_value_on_missing_value() {
fun `uses default value on missing value`() {
val injector = createInjector(
Module { binder: Binder ->
binder.bind(
Expand All @@ -103,41 +106,47 @@ class PropTest {
assertThat(defaultValue.usesDefaultValue()).isEqualTo(DefaultValue.DEFAULT_VALUE)
}

@Test(expected = RequiredNamedAnnotationException::class)
fun throws_on_unnamed_prop() {
val injector = createInjector(
Module { binder: Binder ->
binder.bind(UnnamedProp::class.java)
}
)

injector.getInstance(UnnamedProp::class.java)
@Test
fun `throws on unnamed prop`() {
assertThrows<RequiredNamedAnnotationException> {
val injector = createInjector(
Module { binder: Binder ->
binder.bind(UnnamedProp::class.java)
}
)

injector.getInstance(UnnamedProp::class.java)
}
}

@Test(expected = FailedToCreatePropException::class)
fun throws_on_invalid_default_value() {
val injector = createInjector(
Module { binder: Binder ->
binder.bind(InvalidDefaultValue::class.java)
}
)

injector.getInstance(InvalidDefaultValue::class.java)
@Test
fun `throws on invalid default value`() {
assertThrows<FailedToCreatePropException> {
val injector = createInjector(
Module { binder: Binder ->
binder.bind(InvalidDefaultValue::class.java)
}
)

injector.getInstance(InvalidDefaultValue::class.java)
}
}

@Test(expected = FailedToCreatePropException::class)
fun throws_on_invalid_default_value_even_if_there_is_a_valid_value_in_config() {
val injector = createInjector(
Module { binder: Binder ->
binder.bind(InvalidDefaultValueButValidValue::class.java)
}
)

injector.getInstance(InvalidDefaultValueButValidValue::class.java)
@Test
fun `throws on invalid default value even if there is a valid value in config`() {
assertThrows<FailedToCreatePropException> {
val injector = createInjector(
Module { binder: Binder ->
binder.bind(InvalidDefaultValueButValidValue::class.java)
}
)

injector.getInstance(InvalidDefaultValueButValidValue::class.java)
}
}

@Test
fun can_have_multiple_objects_using_the_same_prop() {
fun `can have multiple objects using the same prop`() {
val injector = createInjector(
Module { binder: Binder ->
binder.bind(ComplexObjects::class.java)
Expand All @@ -153,7 +162,7 @@ class PropTest {
}

@Test
fun can_use_twice_same_prop_in_same_object() {
fun `can use twice same prop in same object`() {
val injector = createInjector(
Module { binder: Binder ->
binder.bind(
Expand All @@ -168,7 +177,7 @@ class PropTest {
}

@Test
fun can_use_both_named_annotations() {
fun `can use both named annotations`() {
val injector = createInjector(
Module { binder: Binder ->
binder.bind(
Expand All @@ -183,29 +192,31 @@ class PropTest {
assertThat(bothNamedAnnotations.stringProp2()).isEqualTo("usp")
}

@Test(expected = FailedToCreatePropException::class)
fun throws_if_deserialization_fails() {
val injector = createInjector(
Module { binder: Binder ->
binder.bind(InvalidJSON::class.java)
}
)

injector.getInstance(InvalidJSON::class.java)
@Test
fun `throws if deserialization fails`() {
assertThrows<FailedToCreatePropException> {
val injector = createInjector(
Module { binder: Binder ->
binder.bind(InvalidJSON::class.java)
}
)

injector.getInstance(InvalidJSON::class.java)
}
}

@Test
fun module_with_no_element_does_not_throw() {
fun `module with no element does not throw`() {
createInjector(Module { })
}

@Test
fun module_with_no_binding_does_not_throw() {
fun `module with no binding does not throw`() {
createInjector(Module { obj: Binder -> obj.requireExplicitBindings() })
}

@Test
fun module_with_provider() {
fun `module with provider`() {
val injector = createInjector(
Module { binder: Binder ->
binder.bind(MyInterface::class.java).toProvider(MyInterfaceProvider::class.java)
Expand All @@ -217,17 +228,19 @@ class PropTest {
assertThat(myInterface.propValue).isEqualTo("awp")
}

@Test(expected = RequiredNamedAnnotationException::class)
fun module_with_empty_named_annotation() {
createInjector(
Module { binder: Binder ->
binder.bind(EmptyNamedAnnotation::class.java)
}
)
@Test
fun `module with empty named annotation`() {
assertThrows<RequiredNamedAnnotationException> {
createInjector(
Module { binder: Binder ->
binder.bind(EmptyNamedAnnotation::class.java)
}
)
}
}

@Test
fun private_module_support() {
fun `private module support`() {
val injector = createInjector(object : PrivateModule() {
override fun configure() {
bind(ComplexObjects::class.java)
Expand All @@ -239,12 +252,12 @@ class PropTest {
}

@Test
fun inline_provider_support() {
fun `inline provider support`() {
createInjector(InlineProviderModule()).getInstance(InjectedObject::class.java)
}

@Test
fun assisted_inject_support() {
fun `assisted inject support`() {
val injector = createInjector(
Module { binder: Binder ->
binder.install(
Expand All @@ -259,7 +272,7 @@ class PropTest {
}

@Test
fun assisted_inject_support_multiple_factory_functions() {
fun `assisted inject support multiple factory functions`() {
val injector = createInjector(
Module { binder: Binder ->
binder.install(FactoryModuleBuilder().build(MyAssistedInjectFactoryModuleMultiple::class.java))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.pleo.prop.archaius

import com.netflix.config.Property
import io.pleo.prop.core.Callback
import io.pleo.prop.core.Prop
import java.time.Instant

Expand All @@ -22,15 +23,11 @@ class ArchaiusProp<T>(private val archaiusProperty: Property<T>) : Prop<T> {
override val changedTimestamp: Instant
get() = Instant.ofEpochMilli(archaiusProperty.changedTimestamp)

override fun addCallback(callback: Runnable) {
archaiusProperty.addCallback(callback)
}

override fun removeAllCallbacks() {
archaiusProperty.removeAllCallbacks()
}
override fun addCallback(callback: Callback<T>) =
archaiusProperty.addCallback {
callback(get())
}

override fun toString(): String {
return archaiusProperty.toString()
}
override fun removeAllCallbacks() = archaiusProperty.removeAllCallbacks()
override fun toString() = archaiusProperty.toString()
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package io.pleo.prop.archaius

import io.pleo.prop.core.Parser
import io.pleo.prop.core.Prop
import io.pleo.prop.core.internal.PropFactory
import java.util.function.Function

class ArchaiusPropFactory : PropFactory {
override fun <T> createProp(
propName: String,
parse: Function<String, T>,
parse: Parser<T>,
defaultValue: T?,
): Prop<T> =
ArchaiusProp(ParsingProperty(propName, parse, defaultValue))
ArchaiusProp(
ParsingProperty(propName, parse, defaultValue),
)
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
package io.pleo.prop.archaius

import com.netflix.config.PropertyWrapper
import io.pleo.prop.core.Parser
import org.slf4j.LoggerFactory
import java.util.function.Function

private val logger = LoggerFactory.getLogger(ParsingProperty::class.java)

class ParsingProperty<T>(
propName: String,
private val parser: Function<String, T>,
private val parser: Parser<T>,
defaultValue: T?,
) : PropertyWrapper<T>(propName, defaultValue) {
@Volatile
private var value: T

init {
value = parseProperty()
}
private var value: T = parseProperty()

private fun parseProperty(): T {
val stringValue = prop.string
Expand All @@ -26,7 +22,7 @@ class ParsingProperty<T>(
}
return defaultValue
}
return parser.apply(stringValue) ?: throw UndefinedPropertyException(this)
return parser(stringValue) ?: throw UndefinedPropertyException(this)
}

override fun propertyChanged() =
Expand Down

0 comments on commit d8d1c63

Please sign in to comment.