diff --git a/kotlin-reporter-junit/.gitignore b/kotlin-reporter-junit/.gitignore new file mode 100644 index 0000000..2e0882f --- /dev/null +++ b/kotlin-reporter-junit/.gitignore @@ -0,0 +1,2 @@ +**/logs/ +**/target/ \ No newline at end of file diff --git a/kotlin-reporter-junit/README.md b/kotlin-reporter-junit/README.md new file mode 100644 index 0000000..4473944 --- /dev/null +++ b/kotlin-reporter-junit/README.md @@ -0,0 +1,51 @@ +# Kotlin reporter integration with JUnit5 + +## Overview + +This simple demo shows how Testomat.io Kotlin reporter works in your project. + +- Some will fail on purpose and other will be disabled for demo. + +## Installation + +1. Clone the repository + +```sh + git clone https://github.com/testomatio/examples.git + ``` +2. Change the directory + +```sh + cd Kotlin-reporter-junit +``` +3. Install dependencies with test skip + +```sh + mvn clean install -DskipTests +``` + + +## Configurations + +**By default, the library runs with properties default values except `testomatio.api.key` and `testomatio.listening`** + +![properties image](img/properties.png) + +Add your project API key to the `testomatio.properties` file ad `testomatio.api.key` + +## Run + +Run tests with + +```bash + mvn test -Dtestomatio.api.key=tstmt_key #if you did not provide it in the `testomatio.properties` file +``` + +where `tstmt_key` is your Testomat.io key from a particular project. + +As a result, you will see a run report in your Project tab -> Runs on Testomat.io. + +
+ demo report result png +
+ diff --git a/kotlin-reporter-junit/img/properties.png b/kotlin-reporter-junit/img/properties.png new file mode 100644 index 0000000..52328ef Binary files /dev/null and b/kotlin-reporter-junit/img/properties.png differ diff --git a/kotlin-reporter-junit/img/runReport.png b/kotlin-reporter-junit/img/runReport.png new file mode 100644 index 0000000..ab2a27d Binary files /dev/null and b/kotlin-reporter-junit/img/runReport.png differ diff --git a/kotlin-reporter-junit/pom.xml b/kotlin-reporter-junit/pom.xml new file mode 100644 index 0000000..9850be6 --- /dev/null +++ b/kotlin-reporter-junit/pom.xml @@ -0,0 +1,99 @@ + + + 4.0.0 + + io.testomat + kotlin-reporter-junit + 1.0-SNAPSHOT + + + UTF-8 + official + 1.8 + + + + + mavenCentral + https://repo1.maven.org/maven2/ + + + + + src/main/kotlin + src/test/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + 2.0.20 + + + compile + compile + + compile + + + + test-compile + test-compile + + test-compile + + + + + + maven-surefire-plugin + 2.22.2 + + + maven-failsafe-plugin + 2.22.2 + + + org.codehaus.mojo + exec-maven-plugin + 1.6.0 + + MainKt + + + + + + + + org.jetbrains.kotlin + kotlin-test-junit5 + 2.0.20 + test + + + io.rest-assured + rest-assured + 5.5.7 + test + + + org.junit.jupiter + junit-jupiter + 5.10.0 + test + + + org.jetbrains.kotlin + kotlin-stdlib + 2.0.20 + + + io.testomat + java-reporter-junit + 0.13.1 + + + + \ No newline at end of file diff --git a/kotlin-reporter-junit/src/main/kotlin/Main.kt b/kotlin-reporter-junit/src/main/kotlin/Main.kt new file mode 100644 index 0000000..9d3401c --- /dev/null +++ b/kotlin-reporter-junit/src/main/kotlin/Main.kt @@ -0,0 +1,4 @@ +package io.testomat + +fun main() { +} \ No newline at end of file diff --git a/kotlin-reporter-junit/src/main/resources/junit-platform.properties b/kotlin-reporter-junit/src/main/resources/junit-platform.properties new file mode 100644 index 0000000..4dcbf9e --- /dev/null +++ b/kotlin-reporter-junit/src/main/resources/junit-platform.properties @@ -0,0 +1,8 @@ +#This line is the mandatory +junit.jupiter.extensions.autodetection.enabled = true + +junit.jupiter.execution.parallel.enabled=true +junit.jupiter.execution.parallel.mode.default=concurrent +junit.jupiter.execution.parallel.mode.classes.default=concurrent +junit.jupiter.execution.parallel.config.strategy=dynamic +junit.jupiter.execution.parallel.config.dynamic.factor=1.0 \ No newline at end of file diff --git a/kotlin-reporter-junit/src/test/kotlin/tests/AdvancedApiTests.kt b/kotlin-reporter-junit/src/test/kotlin/tests/AdvancedApiTests.kt new file mode 100644 index 0000000..28fe56e --- /dev/null +++ b/kotlin-reporter-junit/src/test/kotlin/tests/AdvancedApiTests.kt @@ -0,0 +1,100 @@ +package tests + +import io.restassured.RestAssured.* +import io.restassured.http.ContentType +import org.hamcrest.Matchers.* +import org.junit.jupiter.api.Test +import java.util.concurrent.TimeUnit +import io.testomat.core.annotation.TestId + +class AdvancedApiTests : BaseTest() { + + @Test + @TestId("e2c71562") + fun `response should have expected headers`() { + get("/posts/1") + .then() + .statusCode(200) + .header("Content-Type", containsString("application/json")) + .header("X-Ratelimit-Limit", notNullValue()) + .header("X-Ratelimit-Remaining", notNullValue()) + } + + @Test + @TestId("701867b2") + fun `response time should be acceptable`() { + get("/posts") + .then() + .statusCode(200) + .time(lessThan(5000L), TimeUnit.MILLISECONDS) + } + + @Test + @TestId("5a80e045") + fun `extract and reuse response data`() { + val title: String = get("/posts/1") + .then() + .statusCode(200) + .extract() + .path("title") + + val userId: Int = get("/posts/1") + .then() + .statusCode(200) + .extract() + .path("userId") + + val titles: List = given() + .queryParam("userId", userId) + .`when`() + .get("/posts") + .then() + .statusCode(200) + .extract() + .path("title") + + assert(titles.isNotEmpty()) + assert(titles.all { it.isNotEmpty() }) + } + + @Test + @TestId("8f4f9513") + fun `response body as string and parse`() { + val body: String = get("/posts/1") + .then() + .statusCode(200) + .extract() + .asString() + + assert(body.contains("sunt aut facere")) + assert(body.startsWith("{")) + } + + @Test + @TestId("5c2433ea") + fun `get specific field from list response`() { + val names: List = get("/users") + .then() + .statusCode(200) + .extract() + .path("name") + + assert(names.contains("Leanne Graham")) + assert(names.size == 10) + } + + @Test + @TestId("564a873d") + fun `patch should work via override header`() { + val payload = """{"title":"patched-title"}""" + given() + .contentType(ContentType.JSON) + .header("X-HTTP-Method-Override", "PATCH") + .body(payload) + .`when`() + .post("/posts/1") + .then() + .statusCode(200) + .body("title", equalTo("patched-title")) + } +} diff --git a/kotlin-reporter-junit/src/test/kotlin/tests/AlbumsPhotosTests.kt b/kotlin-reporter-junit/src/test/kotlin/tests/AlbumsPhotosTests.kt new file mode 100644 index 0000000..4ae4df9 --- /dev/null +++ b/kotlin-reporter-junit/src/test/kotlin/tests/AlbumsPhotosTests.kt @@ -0,0 +1,80 @@ +package tests + +import io.restassured.RestAssured.* +import org.hamcrest.Matchers.* +import org.junit.jupiter.api.Test +import io.testomat.core.annotation.TestId + +class AlbumsPhotosTests : BaseTest() { + + @Test + @TestId("baeaa69f") + fun `GET albums returns list`() { + get("/albums") + .then() + .statusCode(200) + .body("size()", greaterThan(0)) + .body("[0].userId", notNullValue()) + .body("[0].id", notNullValue()) + .body("[0].title", notNullValue()) + } + + @Test + @TestId("992b5069") + fun `GET album by id`() { + get("/albums/1") + .then() + .statusCode(200) + .body("userId", equalTo(1)) + .body("title", equalTo("quidem molestiae enim")) + } + + @Test + @TestId("7e8fe52c") + fun `GET photos for album`() { + get("/albums/1/photos") + .then() + .statusCode(200) + .body("size()", greaterThan(0)) + .body("[0].albumId", equalTo(1)) + .body("[0].title", notNullValue()) + .body("[0].url", notNullValue()) + .body("[0].thumbnailUrl", notNullValue()) + } + + @Test + @TestId("b67876bb") + fun `GET photos endpoint`() { + get("/photos") + .then() + .statusCode(200) + .body("size()", greaterThan(0)) + .body("[0].albumId", notNullValue()) + .body("[0].url", containsString("https://")) + } + + @Test + @TestId("779ccfcd") + fun `GET photo by id`() { + get("/photos/1") + .then() + .statusCode(200) + .body("id", equalTo(1)) + .body("albumId", equalTo(1)) + .body("title", notNullValue()) + .body("url", notNullValue()) + } + + @Test + @TestId("581d506a") + fun `GET albums by userId`() { + given() + .queryParam("userId", 1) + .`when`() + .get("/albums") + .then() + .statusCode(200) + .body("size()", greaterThan(0)) + .body("userId", everyItem(equalTo(1))) + } +} diff --git a/kotlin-reporter-junit/src/test/kotlin/tests/BaseTest.kt b/kotlin-reporter-junit/src/test/kotlin/tests/BaseTest.kt new file mode 100644 index 0000000..dd18f91 --- /dev/null +++ b/kotlin-reporter-junit/src/test/kotlin/tests/BaseTest.kt @@ -0,0 +1,14 @@ +package tests + +import io.restassured.RestAssured.baseURI +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.TestInstance + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +abstract class BaseTest { + + @BeforeAll + fun setup() { + baseURI = "https://jsonplaceholder.typicode.com" + } +} diff --git a/kotlin-reporter-junit/src/test/kotlin/tests/CrudApiTests.kt b/kotlin-reporter-junit/src/test/kotlin/tests/CrudApiTests.kt new file mode 100644 index 0000000..532b2d7 --- /dev/null +++ b/kotlin-reporter-junit/src/test/kotlin/tests/CrudApiTests.kt @@ -0,0 +1,105 @@ +package tests + +import io.restassured.RestAssured.* +import io.restassured.http.ContentType +import org.hamcrest.Matchers.* +import org.junit.jupiter.api.Test +import io.testomat.core.annotation.TestId + +class CrudApiTests : BaseTest() { + + @Test + @TestId("c23988ce") + fun `GET posts returns list`() { + get("/posts") + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("size()", greaterThan(0)) + .body("[0].id", notNullValue()) + .body("[0].title", notNullValue()) + } + + @Test + @TestId("05272ac5") + fun `GET post by id returns single object`() { + get("/posts/1") + .then() + .statusCode(200) + .body("id", equalTo(1)) + .body("title", notNullValue()) + .body("body", notNullValue()) + .body("userId", notNullValue()) + } + + @Test + @TestId("973e6c28") + fun `POST creates a new resource`() { + val payload = """{"title":"foo","body":"bar","userId":1}""" + given() + .contentType(ContentType.JSON) + .body(payload) + .`when`() + .post("/posts") + .then() + .statusCode(201) + .body("title", equalTo("foo")) + .body("body", equalTo("bar")) + .body("id", notNullValue()) + } + + @Test + @TestId("87fd940a") + fun `PUT updates an existing resource`() { + val payload = """{"id":1,"title":"updated","body":"new body","userId":1}""" + given() + .contentType(ContentType.JSON) + .body(payload) + .`when`() + .put("/posts/1") + .then() + .statusCode(200) + .body("title", equalTo("updated")) + .body("body", equalTo("new body")) + } + + @Test + @TestId("cb49c380") + fun `DELETE returns empty response`() { + delete("/posts/1") + .then() + .statusCode(200) + } + + @Test + @TestId("a75e5a08") + fun `GET nonexistent resource returns 404`() { + get("/posts/99999") + .then() + .statusCode(404) + } + + @Test + @TestId("43d91d1e") + fun `GET nested posts comments`() { + get("/posts/1/comments") + .then() + .statusCode(200) + .body("size()", greaterThan(0)) + .body("[0].email", notNullValue()) + .body("[0].name", notNullValue()) + } + + @Test + @TestId("1f4fcbb6") + fun `GET post with query param`() { + given() + .queryParam("postId", 1) + .`when`() + .get("/comments") + .then() + .statusCode(200) + .body("size()", greaterThan(0)) + .body("postId", everyItem(equalTo(1))) + } +} diff --git a/kotlin-reporter-junit/src/test/kotlin/tests/ErrorScenariosTests.kt b/kotlin-reporter-junit/src/test/kotlin/tests/ErrorScenariosTests.kt new file mode 100644 index 0000000..7719382 --- /dev/null +++ b/kotlin-reporter-junit/src/test/kotlin/tests/ErrorScenariosTests.kt @@ -0,0 +1,77 @@ +package tests + +import io.restassured.RestAssured.* +import io.restassured.http.ContentType +import org.hamcrest.Matchers.* +import org.junit.jupiter.api.Test +import io.testomat.core.annotation.TestId + +class ErrorScenariosTests : BaseTest() { + + @Test + @TestId("4170370f") + fun `GET invalid endpoint returns 404`() { + get("/nonexistent") + .then() + .statusCode(404) + } + + @Test + @TestId("a4d30df0") + fun `POST with empty body creates mock resource`() { + given() + .contentType(ContentType.JSON) + .body("{}") + .`when`() + .post("/posts") + .then() + .statusCode(201) + .body("id", notNullValue()) + } + + @Test + @TestId("043d0e66") + fun `POST does not require userId field`() { + given() + .contentType(ContentType.JSON) + .body("""{"title":"no-user"}""") + .`when`() + .post("/posts") + .then() + .statusCode(201) + .body("title", equalTo("no-user")) + .body("userId", nullValue()) + } + + @Test + @TestId("ca34f39b") + fun `PUT on nonexistent resource`() { + val payload = """{"title":"nowhere"}""" + given() + .contentType(ContentType.JSON) + .body(payload) + .`when`() + .put("/posts/99999") + .then() + .statusCode(500) + } + + @Test + @TestId("b33e12b4") + fun `DELETE nonexistent resource`() { + delete("/posts/99999") + .then() + .statusCode(200) + } + + @Test + @TestId("f2844d62") + fun `invalid query param types`() { + given() + .queryParam("id", "abc") + .`when`() + .get("/posts") + .then() + .statusCode(200) + } +} diff --git a/kotlin-reporter-junit/src/test/kotlin/tests/FilterApiTests.kt b/kotlin-reporter-junit/src/test/kotlin/tests/FilterApiTests.kt new file mode 100644 index 0000000..75b3063 --- /dev/null +++ b/kotlin-reporter-junit/src/test/kotlin/tests/FilterApiTests.kt @@ -0,0 +1,86 @@ +package tests + +import io.restassured.RestAssured.* +import org.hamcrest.Matchers.* +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource +import io.testomat.core.annotation.TestId + +class FilterApiTests : BaseTest() { + + @Test + @TestId("2f5f0714") + fun `GET comments by postId filter`() { + given() + .queryParam("postId", 1) + .`when`() + .get("/comments") + .then() + .statusCode(200) + .body("size()", greaterThan(0)) + .body("postId", everyItem(equalTo(1))) + } + + @ParameterizedTest + @ValueSource(strings = ["/posts/1/comments", "/comments?postId=1"]) + @TestId("e9f201c0") + fun `comments for post 1 returned by both endpoints`(path: String) { + get(path) + .then() + .statusCode(200) + .body("size()", greaterThan(0)) + .body("[0].postId", equalTo(1)) + } + + @Test + @TestId("d7cef041") + fun `GET limited results via query params`() { + given() + .queryParam("userId", 1) + .queryParam("completed", true) + .`when`() + .get("/todos") + .then() + .statusCode(200) + .body("size()", greaterThan(0)) + .body("userId", everyItem(equalTo(1))) + .body("completed", everyItem(equalTo(true))) + } + + @Test + @TestId("5483a8cd") + fun `GET all todos`() { + get("/todos") + .then() + .statusCode(200) + .body("size()", greaterThan(0)) + .body("[0].userId", notNullValue()) + .body("[0].title", notNullValue()) + .body("[0].completed", notNullValue()) + } + + @Test + @TestId("b4820150") + fun `GET users endpoint returns collection`() { + get("/users") + .then() + .statusCode(200) + .body("size()", equalTo(10)) + .body("[0].name", equalTo("Leanne Graham")) + .body("[0].email", equalTo("Sincere@april.biz")) + } + + @Test + @TestId("c82a81f9") + fun `GET todos by userId returns matching items`() { + given() + .queryParam("userId", 1) + .`when`() + .get("/todos") + .then() + .statusCode(200) + .body("size()", greaterThan(0)) + .body("userId", everyItem(equalTo(1))) + } +} diff --git a/kotlin-reporter-junit/src/test/kotlin/tests/ParameterizedApiTests.kt b/kotlin-reporter-junit/src/test/kotlin/tests/ParameterizedApiTests.kt new file mode 100644 index 0000000..1bdb3e1 --- /dev/null +++ b/kotlin-reporter-junit/src/test/kotlin/tests/ParameterizedApiTests.kt @@ -0,0 +1,54 @@ +package tests + +import io.restassured.RestAssured.* +import org.hamcrest.Matchers.* +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.* +import io.testomat.core.annotation.TestId + +class ParameterizedApiTests : BaseTest() { + + @ParameterizedTest + @CsvSource( + "1, Leanne Graham, Sincere@april.biz", + "2, Ervin Howell, Shanna@melissa.tv", + "3, Clementine Bauch, Nathan@yesenia.net" + ) + @TestId("c1ba7a90") + fun `GET user by id returns expected name and email`(id: Int, name: String, email: String) { + get("/users/$id") + .then() + .statusCode(200) + .body("name", equalTo(name)) + .body("email", equalTo(email)) + } + + @ParameterizedTest + @ValueSource(ints = [1, 2, 3, 5, 10]) + @TestId("688a9b2d") + fun `GET post by id returns 200 for valid ids`(id: Int) { + get("/posts/$id") + .then() + .statusCode(200) + .body("id", equalTo(id)) + } + + @ParameterizedTest + @MethodSource("postIdsWithTitles") + @TestId("04ad8cce") + fun `GET post by id returns expected title`(id: Int, title: String) { + get("/posts/$id") + .then() + .statusCode(200) + .body("title", equalTo(title)) + } + + companion object { + @JvmStatic + fun postIdsWithTitles() = listOf( + Arguments.of(1, "sunt aut facere repellat provident occaecati excepturi optio reprehenderit"), + Arguments.of(2, "qui est esse"), + Arguments.of(3, "ea molestias quasi exercitationem repellat qui ipsa sit aut") + ) + } +} diff --git a/kotlin-reporter-junit/src/test/kotlin/tests/UserApiTests.kt b/kotlin-reporter-junit/src/test/kotlin/tests/UserApiTests.kt new file mode 100644 index 0000000..833cef9 --- /dev/null +++ b/kotlin-reporter-junit/src/test/kotlin/tests/UserApiTests.kt @@ -0,0 +1,66 @@ +package tests + +import io.restassured.RestAssured.* +import org.hamcrest.Matchers.* +import org.junit.jupiter.api.Test +import io.testomat.core.annotation.TestId + +class UserApiTests : BaseTest() { + + companion object { + private const val USERS = "/users" + private const val USER_1 = "/users/1" + } + + private fun getUser1() = + get(USER_1) + .then() + .statusCode(200) + + @Test + @TestId("7f6a9557") + fun `GET users returns list of 10 users`() { + get(USERS) + .then() + .statusCode(200) + .body("size()", equalTo(10)) + } + + @Test + @TestId("31fe6da1") + fun `GET user by id returns correct structure`() { + getUser1() + .body("name", equalTo("Leanne Graham")) + .body("username", equalTo("Bret")) + .body("email", equalTo("Sincere@april.biz")) + .body("phone", notNullValue()) + .body("website", notNullValue()) + } + + @Test + @TestId("52ef376a") + fun `user has nested address object`() { + getUser1() + .body("address.street", equalTo("Kulas Light")) + .body("address.suite", equalTo("Apt. 556")) + .body("address.city", equalTo("Gwenborough")) + .body("address.zipcode", equalTo("92998-3874")) + } + + @Test + @TestId("fe373ddf") + fun `user has company info`() { + getUser1() + .body("company.name", equalTo("Romaguera-Crona")) + .body("company.catchPhrase", notNullValue()) + .body("company.bs", notNullValue()) + } + + @Test + @TestId("58de887c") + fun `user has geo location`() { + getUser1() + .body("address.geo.lat", notNullValue()) + .body("address.geo.lng", notNullValue()) + } +} \ No newline at end of file