Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Kotlin specific assertion helpers #1001

Closed
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
2d6ca09
Add Kotlin Assertion methods
JLLeitschuh Aug 4, 2017
451930e
Cleanup build and ensure kotlin sources are included in api JAR
JLLeitschuh Aug 4, 2017
b5279aa
Cleanup spotless problems on existing kotlin code
JLLeitschuh Aug 4, 2017
7c9102c
Remove duplicate kotlin plugin application
JLLeitschuh Aug 4, 2017
43c956e
Revert changes to using Gradle Kotlin DSL
JLLeitschuh Aug 30, 2017
d995cde
Remove duck emoji from kotlin assertion documentation
JLLeitschuh Aug 30, 2017
bc0c5e3
Use kotlin-stdlib instead of kotlin-stdlib-jre8
JLLeitschuh Aug 30, 2017
05f037a
Add jvmTarget as 1.8
JLLeitschuh Aug 30, 2017
e580a04
Add kotlin assertThrows that takes a message
JLLeitschuh Aug 30, 2017
1a0bce3
Update writing-tests section to have kotlin examples
JLLeitschuh Aug 31, 2017
ee5db7d
Clarify comment in AssertionsDemoKotlin
JLLeitschuh Aug 31, 2017
514318c
Add release-notes-5.0.0-GA with mention of Kotlin assertions
JLLeitschuh Aug 31, 2017
869d4eb
Fix problem with javdoc generation under JDK 9
JLLeitschuh Aug 31, 2017
c638df6
Add a sanity test for the Kotlin assertThrows method
JLLeitschuh Aug 31, 2017
6bf57fa
Cleanup JavaDocs to point out additional Kotlin assertions that exist
JLLeitschuh Aug 31, 2017
9a76a15
Merge branch 'master' into feature/kotlin-assertion-helpers
JLLeitschuh Sep 14, 2017
b3ab661
Cleanup Kolin Assertion PR code post merge to master
JLLeitschuh Sep 14, 2017
893adba
Merge branch 'master' into feature/kotlin-assertion-helpers
JLLeitschuh Sep 15, 2017
efebdd8
Change Assertions.kt to be versioned since 5.1
JLLeitschuh Sep 15, 2017
0cc09cb
Merge branch 'master' into feature/kotlin-assertion-helpers
JLLeitschuh Sep 19, 2017
78a62bb
Merge branch 'master' into feature/kotlin-assertion-helpers
JLLeitschuh Oct 6, 2017
2007a89
Resolve type signature change in AssertionsAssertAllKotlinTests
JLLeitschuh Oct 6, 2017
b0bfc63
Merge branch 'master' into feature/kotlin-assertion-helpers
marcphilipp Nov 9, 2017
99b7569
Reformat existing kotlin sources to use spaces instead of tabs
JLLeitschuh Nov 9, 2017
0fa5c98
Use jvmTarget of 1.8 for KotlinCompile
JLLeitschuh Nov 9, 2017
e226327
Typo: Junit -> JUnit
JLLeitschuh Nov 10, 2017
6fdbf08
Make ExecutableStream private
JLLeitschuh Nov 13, 2017
a726c91
Merge branch 'master' into feature/kotlin-assertion-helpers
JLLeitschuh Nov 13, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,12 @@ allprojects { subproj ->
options.compilerArgs += '-parameters'
}

tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile) {
kotlinOptions {
jvmTarget = '1.8'
}
}

checkstyle {
toolVersion = '7.6'
}
Expand Down Expand Up @@ -537,6 +543,13 @@ subprojects { subproj ->
replaceRegex 'Empty line between last method and class closure', /\n([\s]+)}\n}\n$/, '\n$1}\n\n}\n'
replaceRegex 'Remove line breaks between consecutive closing parentheses', /\)\n[\s]+\)\n/, '))\n'
}

kotlin {
ktlint("0.9.0")
Copy link
Contributor Author

@JLLeitschuh JLLeitschuh Aug 4, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ktlint is the only kotlin linter that exists at present. I use it for all of my projects and it's pretty nice.

If you want to have it use tabs instead of spaces for indentation it can be configured using an .editorconfig file in the root directory of the project.

licenseHeaderFile headerFile
trimTrailingWhitespace()
endWithNewline()
}
}

afterEvaluate {
Expand Down
2 changes: 2 additions & 0 deletions documentation/documentation.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ dependencies {
testImplementation(project(path: ':junit-jupiter-params', configuration: 'shadow'))
testImplementation(project(':junit-platform-runner'))
testImplementation(project(':junit-platform-launcher'))
testImplementation("org.jetbrains.kotlin:kotlin-stdlib")

// Include junit-platform-console so that the JUnit Gradle plugin
// uses the local version of the ConsoleLauncher.
Expand Down Expand Up @@ -91,6 +92,7 @@ asciidoctor {
'revnumber' : project.version,
'mainDir': project.sourceSets.main.java.srcDirs[0],
'testDir': project.sourceSets.test.java.srcDirs[0],
'kotlinTestDir' : project.sourceSets.test.kotlin.srcDirs[0],
'generatedAsciiDocInputDir': generatedAsciiDocInputDir,
'testResourcesDir': project.sourceSets.test.resources.srcDirs[0],
'outdir': outputDir.absolutePath,
Expand Down
6 changes: 4 additions & 2 deletions documentation/src/docs/asciidoc/release-notes-5.1.0-M1.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

*Date of Release:* ❓

*Scope:*
*Scope:* Addition of Kotlin assertion helpers.

For a complete list of all _closed_ issues and pull requests for this release, consult the
link:{junit5-repo}+/milestone/14?closed=1+[5.1 M1] milestone page in the JUnit repository
Expand Down Expand Up @@ -81,7 +81,9 @@ on GitHub.
`store.getOrComputeIfAbsent(X.class, key \-> new X(), X.class)` can now replaced with
`store.getOrComputeIfAbsent(X.class)`.
* Support new JUnit Platform `ModuleSelector`.

* New Kotlin friendly assertions added as top-level functions in the `org.junit.jupiter.api` package.
** `assertAll` that takes `Stream<() -> Unit>` or `vararg () -> Unit`.
** `assertThrow` that uses Kotlin reified generics.

[[release-notes-5.1.0-junit-vintage]]
==== JUnit Vintage
Expand Down
10 changes: 10 additions & 0 deletions documentation/src/docs/asciidoc/writing-tests.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,16 @@ are `static` methods in the `{Assertions}` class.
include::{testDir}/example/AssertionsDemo.java[tags=user_guide]
----

Junit Jupiter also comes with a few assertion methods that lend themselves well to being
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JUnit

used in https://kotlinlang.org/[Kotlin]. All JUnit Jupiter Kotlin assertions are top-level
functions in the `org.junit.jupiter.api` package.

// TODO: Change to using kotlin language highlighting after switch to rouge syntax highlighter
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the status of switching to the rouge syntax highlighter?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still blocked by asciidoctor/asciidoctor#1040.

[source,java,indent=0]
----
include::{kotlinTestDir}/example/AssertionsDemoKotlin.kt[tags=user_guide]
----

[[writing-tests-assertions-third-party]]
==== Third-party Assertion Libraries

Expand Down
20 changes: 0 additions & 20 deletions documentation/src/test/java/example/AssertionsDemo.java
Original file line number Diff line number Diff line change
Expand Up @@ -143,23 +143,3 @@ private static String greeting() {
}
// end::user_guide[]
// @formatter:on

class Person {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't access this class in AssertionsDemoKotlin without making it public. It was just easier to make Person a kotlin data class that both tests use.


private final String firstName;
private final String lastName;

Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}

String getFirstName() {
return firstName;
}

String getLastName() {
return lastName;
}

}
55 changes: 55 additions & 0 deletions documentation/src/test/kotlin/example/AssertionsDemoKotlin.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright 2015-2017 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* http://www.eclipse.org/legal/epl-v20.html
*/
package example

// tag::user_guide[]
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertAll
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.assertThrows

class AssertionsDemoKotlin {

// end::user_guide[]
val person = Person("John", "Doe")
val people = setOf(person, Person("James", "Doe"))

// tag::user_guide[]
@Test
fun `grouped assertions`() {
assertAll("person",
{ assertEquals("John", person.firstName) },
{ assertEquals("Doe", person.lastName) }
)
}

@Test
fun `exception testing`() {
val exception = assertThrows<IllegalArgumentException> ("Should throw an exception") {
throw IllegalArgumentException("a message")
}
assertEquals("a message", exception.message)
}

@Test
fun `assertions from a stream`() {
assertAll(
"people with name starting with J",
people
.stream()
.map {
// This mapping returns Stream<() -> Unit>
{ assertTrue(it.firstName.startsWith("J")) }
}
)
}
}
// end::user_guide[]
12 changes: 12 additions & 0 deletions documentation/src/test/kotlin/example/Person.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* Copyright 2015-2017 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* http://www.eclipse.org/legal/epl-v20.html
*/
package example

data class Person(val firstName: String, val lastName: String)
24 changes: 22 additions & 2 deletions junit-jupiter-api/junit-jupiter-api.gradle
Original file line number Diff line number Diff line change
@@ -1,12 +1,32 @@
dependencies {
api("org.opentest4j:opentest4j:${ota4jVersion}")
api(project(':junit-platform-commons'))
api(project(":junit-platform-commons"))
compileOnly("org.jetbrains.kotlin:kotlin-stdlib")
}

jar {
manifest {
attributes(
'Automatic-Module-Name': 'org.junit.jupiter.api'
'Automatic-Module-Name': 'org.junit.jupiter.api'
)
}
}

configurations {
apiElements {
/*
* Needed to configure kotlin to work correctly with the "java-library" plugin.
* See:
* https://docs.gradle.org/current/userguide/java_library_plugin.html#sec:java_library_known_issues
* https://youtrack.jetbrains.com/issue/KT-18497
*/
outgoing
.variants
.getByName("classes")
.artifact(
"file" : compileKotlin.destinationDir,
"type" : "java-classes-directory",
"builtBy" : compileKotlin
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
* {@code Assertions} is a collection of utility methods that support asserting
* conditions in tests.
*
* <p>Additional <a href="https://kotlinlang.org/">Kotlin</a> assertions can be found
* as top-level functions on the {@link org.junit.jupiter.api} package.
*
* <p>Unless otherwise noted, a <em>failed</em> assertion will throw an
* {@link org.opentest4j.AssertionFailedError} or a subclass thereof.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The compiler will generate a class called AssertionsKt to store these methods inside of. Is this acceptable? We can rename the generated class if necessary.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's fine

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool!

* Copyright 2015-2017 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* http://www.eclipse.org/legal/epl-v20.html
*/
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even though this file is called Assertions.kt what really happens is this creates a class called AssertionsKt. Thus preventing the naming conflict.
If the team would like to be more specific about the name of the class created we could add something like @file:JvmName("AssertionsForKotlin") to the top of the file.

This will require some monkeying with spotless due to this issue:
diffplug/spotless#136

Does the team mind that the kotlin compiler just generates a class and uses its own naming convention? If not then I don't need to worry about the spotless bug.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this naming convention likely to change in the future?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, it shouldn't.

@file:API(status = EXPERIMENTAL, since = "5.1")
package org.junit.jupiter.api

import org.apiguardian.api.API
import org.apiguardian.api.API.Status.EXPERIMENTAL
import org.junit.jupiter.api.function.Executable
import java.util.function.Supplier
import java.util.stream.Stream

/**
* [Stream] of functions to be executed.
*/
internal typealias ExecutableStream = Stream<() -> Unit>
internal fun ExecutableStream.convert() = map { Executable(it) }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make these private?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Effectively they are. internal is different from java's "package private".
"Package private" in java means that if I (a consumer) create a file in a package called org.junit.api then I can access "package private" things. The same is not true in Kotlin for internal.
Internal is internal only to source code that is compiled together.

I can make them private if you wish though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I was able to make them private.
I remember that in earlier versions of kotlin you couldn't call private things inside of inline functions. I'm not certain if that limitation still exists. But it doesn't seem to be a problem here.


/**
* @see Assertions.assertAll
*/
fun assertAll(executables: ExecutableStream) =
Assertions.assertAll(executables.convert())

/**
* @see Assertions.assertAll
*/
fun assertAll(heading: String?, executables: ExecutableStream) =
Assertions.assertAll(heading, executables.convert())

/**
* @see Assertions.assertAll
*/
fun assertAll(vararg executables: () -> Unit) =
assertAll(executables.toList().stream())

/**
* @see Assertions.assertAll
*/
fun assertAll(heading: String?, vararg executables: () -> Unit) =
assertAll(heading, executables.toList().stream())

/**
* Example usage:
* ```kotlin
* val exception = assertThrows<IllegalArgumentException> {
* throw IllegalArgumentException("Talk to a duck")
* }
* assertEquals("Talk to a duck", exception.message)
* ```
* @see Assertions.assertThrows
*/
inline fun <reified T : Throwable> assertThrows(noinline executable: () -> Unit): T =
Assertions.assertThrows(T::class.java, Executable(executable))

/**
* Example usage:
* ```kotlin
* val exception = assertThrows<IllegalArgumentException>("Should throw an Exception") {
* throw IllegalArgumentException("Talk to a duck")
* }
* assertEquals("Talk to a duck", exception.message)
* ```
* @see Assertions.assertThrows
*/
inline fun <reified T : Throwable> assertThrows(message: String, noinline executable: () -> Unit): T =
assertThrows({ message }, executable)

/**
* Example usage:
* ```kotlin
* val exception = assertThrows<IllegalArgumentException>({ "Should throw an Exception" }) {
* throw IllegalArgumentException("Talk to a duck")
* }
* assertEquals("Talk to a duck", exception.message)
* ```
* @see Assertions.assertThrows
*/
inline fun <reified T : Throwable> assertThrows(noinline message: () -> String, noinline executable: () -> Unit): T =
Assertions.assertThrows(T::class.java, Executable(executable), Supplier {
/*
* This is a hacky workaround due to a bug in how the JDK 9 JavaDoc code generator interacts with the
* generated Kotlin Bytecode.
* https://youtrack.jetbrains.com/issue/KT-20025
*/
message()
})
2 changes: 1 addition & 1 deletion junit-jupiter-engine/junit-jupiter-engine.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ dependencies {
testImplementation(project(path: ':junit-platform-engine', configuration: 'testArtifacts'))
testImplementation("org.assertj:assertj-core:${assertJVersion}")
testImplementation("org.mockito:mockito-core:${mockitoVersion}")
testImplementation("org.jetbrains.kotlin:kotlin-stdlib-jre8:${kotlinVersion}")
testImplementation("org.jetbrains.kotlin:kotlin-stdlib:${kotlinVersion}")

// Include junit-platform-console so that the JUnit Gradle plugin
// uses the local version of the ConsoleLauncher.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ void assertAllWithExecutableThatThrowsBlacklistedException() {
}

@SafeVarargs
private static void assertExpectedExceptionTypes(MultipleFailuresError multipleFailuresError,
static void assertExpectedExceptionTypes(MultipleFailuresError multipleFailuresError,
Class<? extends Throwable>... exceptionTypes) {

assertNotNull(multipleFailuresError, "MultipleFailuresError");
Expand Down
Loading