├── .editorconfig ├── .github ├── maven-cd-settings.xml ├── maven-ci-settings.xml └── workflows │ ├── ci-4.x.yml │ ├── ci-5.x-stable.yml │ ├── ci-5.x.yml │ ├── ci-matrix-5.x.yml │ ├── ci.yml │ └── deploy.yml ├── .gitignore ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── LICENSE ├── README.md ├── mvnw ├── mvnw.cmd ├── pom.xml └── src ├── main ├── asciidoc │ └── index.adoc ├── java │ ├── examples │ │ ├── Examples.java │ │ ├── LifecycleExampleTest.java │ │ ├── RunTestOnContextExampleTest.java │ │ └── package-info.java │ ├── io │ │ └── vertx │ │ │ └── junit5 │ │ │ ├── Checkpoint.java │ │ │ ├── CountingCheckpoint.java │ │ │ ├── ParameterClosingConsumer.java │ │ │ ├── RunTestOnContext.java │ │ │ ├── ScopedObject.java │ │ │ ├── Timeout.java │ │ │ ├── VertxExtension.java │ │ │ ├── VertxExtensionParameterProvider.java │ │ │ ├── VertxParameterProvider.java │ │ │ ├── VertxTestContext.java │ │ │ ├── VertxTestContextParameterProvider.java │ │ │ └── package-info.java │ └── module-info.java └── resources │ └── META-INF │ └── services │ └── io.vertx.junit5.VertxExtensionParameterProvider └── test └── java ├── io └── vertx │ └── junit5 │ └── tests │ ├── AbstractTest.java │ ├── AsyncBeforeAllTest.java │ ├── AsyncBeforeCombinedTest.java │ ├── AsyncBeforeEachTest.java │ ├── ConcreteTest.java │ ├── CountingCheckpointTest.java │ ├── CustomizedRunOnContextExtensionTest.java │ ├── IntegrationTest.java │ ├── RunOnContextExtensionTest.java │ ├── StaticRunOnContextExtensionTest.java │ ├── VertxExtensionCompleteLifecycleInjectionTest.java │ ├── VertxExtensionTest.java │ ├── VertxParameterProviderTest.java │ └── VertxTestContextTest.java └── module-info.java /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | trim_trailing_whitespace = true 8 | end_of_line = lf 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.github/maven-cd-settings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | false 20 | 21 | 22 | 23 | vertx-snapshots-repository 24 | ${env.VERTX_NEXUS_USERNAME} 25 | ${env.VERTX_NEXUS_PASSWORD} 26 | 27 | 28 | 29 | 30 | 31 | google-mirror 32 | 33 | true 34 | 35 | 36 | 37 | google-maven-central 38 | GCS Maven Central mirror EU 39 | https://maven-central.storage-download.googleapis.com/maven2/ 40 | 41 | true 42 | 43 | 44 | false 45 | 46 | 47 | 48 | 49 | 50 | google-maven-central 51 | GCS Maven Central mirror 52 | https://maven-central.storage-download.googleapis.com/maven2/ 53 | 54 | true 55 | 56 | 57 | false 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /.github/maven-ci-settings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | false 20 | 21 | 22 | 23 | google-mirror 24 | 25 | true 26 | 27 | 28 | 29 | google-maven-central 30 | GCS Maven Central mirror EU 31 | https://maven-central.storage-download.googleapis.com/maven2/ 32 | 33 | true 34 | 35 | 36 | false 37 | 38 | 39 | 40 | 41 | 42 | google-maven-central 43 | GCS Maven Central mirror 44 | https://maven-central.storage-download.googleapis.com/maven2/ 45 | 46 | true 47 | 48 | 49 | false 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /.github/workflows/ci-4.x.yml: -------------------------------------------------------------------------------- 1 | name: vertx-junit5 (4.x) 2 | on: 3 | schedule: 4 | - cron: '0 4 * * *' 5 | jobs: 6 | CI: 7 | strategy: 8 | matrix: 9 | include: 10 | - os: ubuntu-latest 11 | jdk: 8 12 | - os: ubuntu-latest 13 | jdk: 17 14 | uses: ./.github/workflows/ci.yml 15 | with: 16 | branch: 4.x 17 | jdk: ${{ matrix.jdk }} 18 | os: ${{ matrix.os }} 19 | secrets: inherit 20 | Deploy: 21 | if: ${{ github.repository_owner == 'eclipse-vertx' && (github.event_name == 'push' || github.event_name == 'schedule') }} 22 | needs: CI 23 | uses: ./.github/workflows/deploy.yml 24 | with: 25 | branch: 4.x 26 | jdk: 8 27 | secrets: inherit 28 | -------------------------------------------------------------------------------- /.github/workflows/ci-5.x-stable.yml: -------------------------------------------------------------------------------- 1 | name: vertx-junit5 (5.x-stable) 2 | on: 3 | push: 4 | branches: 5 | - '5.[0-9]+' 6 | pull_request: 7 | branches: 8 | - '5.[0-9]+' 9 | schedule: 10 | - cron: '0 6 * * *' 11 | jobs: 12 | CI-CD: 13 | uses: ./.github/workflows/ci-matrix-5.x.yml 14 | secrets: inherit 15 | with: 16 | branch: ${{ github.event_name == 'schedule' && vars.VERTX_5_STABLE_BRANCH || github.event.pull_request.head.sha || github.ref_name }} 17 | -------------------------------------------------------------------------------- /.github/workflows/ci-5.x.yml: -------------------------------------------------------------------------------- 1 | name: vertx-junit5 (5.x) 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | branches: 8 | - master 9 | schedule: 10 | - cron: '0 5 * * *' 11 | jobs: 12 | CI-CD: 13 | uses: ./.github/workflows/ci-matrix-5.x.yml 14 | secrets: inherit 15 | with: 16 | branch: ${{ github.event.pull_request.head.sha || github.ref_name }} 17 | -------------------------------------------------------------------------------- /.github/workflows/ci-matrix-5.x.yml: -------------------------------------------------------------------------------- 1 | name: CI matrix (5.x) 2 | on: 3 | workflow_call: 4 | inputs: 5 | branch: 6 | required: true 7 | type: string 8 | jobs: 9 | CI: 10 | strategy: 11 | matrix: 12 | include: 13 | - os: ubuntu-latest 14 | jdk: 11 15 | - os: ubuntu-latest 16 | jdk: 21 17 | uses: ./.github/workflows/ci.yml 18 | with: 19 | branch: ${{ inputs.branch }} 20 | jdk: ${{ matrix.jdk }} 21 | os: ${{ matrix.os }} 22 | secrets: inherit 23 | Deploy: 24 | if: ${{ github.repository_owner == 'eclipse-vertx' && (github.event_name == 'push' || github.event_name == 'schedule') }} 25 | needs: CI 26 | uses: ./.github/workflows/deploy.yml 27 | with: 28 | branch: ${{ inputs.branch }} 29 | jdk: 11 30 | secrets: inherit 31 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | workflow_call: 4 | inputs: 5 | branch: 6 | required: true 7 | type: string 8 | jdk: 9 | default: 8 10 | type: string 11 | os: 12 | default: ubuntu-latest 13 | type: string 14 | jobs: 15 | Test: 16 | name: Run tests 17 | runs-on: ${{ inputs.os }} 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v2 21 | with: 22 | ref: ${{ inputs.branch }} 23 | - name: Install JDK 24 | uses: actions/setup-java@v2 25 | with: 26 | java-version: ${{ inputs.jdk }} 27 | distribution: temurin 28 | - name: Run tests 29 | run: mvn -s .github/maven-ci-settings.xml -q clean verify -B 30 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | on: 3 | workflow_call: 4 | inputs: 5 | branch: 6 | required: true 7 | type: string 8 | jdk: 9 | default: 8 10 | type: string 11 | jobs: 12 | Deploy: 13 | name: Deploy to OSSRH 14 | runs-on: ubuntu-latest 15 | env: 16 | VERTX_NEXUS_USERNAME: ${{ secrets.VERTX_NEXUS_USERNAME }} 17 | VERTX_NEXUS_PASSWORD: ${{ secrets.VERTX_NEXUS_PASSWORD }} 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v2 21 | with: 22 | ref: ${{ inputs.branch }} 23 | - name: Install JDK 24 | uses: actions/setup-java@v2 25 | with: 26 | java-version: ${{ inputs.jdk }} 27 | distribution: temurin 28 | - name: Get project version 29 | run: echo "PROJECT_VERSION=$(mvn org.apache.maven.plugins:maven-help-plugin:evaluate -Dexpression=project.version -q -DforceStdout | grep -v '\[')" >> $GITHUB_ENV 30 | - name: Maven deploy 31 | if: ${{ endsWith(env.PROJECT_VERSION, '-SNAPSHOT') }} 32 | run: mvn deploy -s .github/maven-cd-settings.xml -DskipTests -B 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | target/ 3 | *.iml 4 | .DS_Store 5 | .classpath 6 | .project 7 | .settings/ 8 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eclipse-vertx/vertx-junit5/c9b97c8497e53ff65da831cf3e3b75464b7963e1/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status (5.x)](https://github.com/eclipse-vertx/vertx-junit5/actions/workflows/ci-5.x.yml/badge.svg)](https://github.com/eclipse-vertx/vertx-junit5/actions/workflows/ci-5.x.yml) 2 | [![Build Status (4.x)](https://github.com/eclipse-vertx/vertx-junit5/actions/workflows/ci-4.x.yml/badge.svg)](https://github.com/eclipse-vertx/vertx-codegen/actions/workflows/ci-4.x.yml) 3 | 4 | # Vert.x JUnit 5 integration 5 | 6 | This module offers integration and support for writing Vert.x tests with JUnit 5. 7 | 8 | ## Documentation 9 | 10 | * [Manual](https://vertx.io/docs/vertx-junit5/java/) 11 | * [API](https://vertx.io/docs/apidocs/) 12 | 13 | ## License 14 | 15 | Copyright (c) 2018 Red Hat, Inc. 16 | 17 | Licensed under the Apache License, Version 2.0 (the "License"); 18 | you may not use this file except in compliance with the License. 19 | You may obtain a copy of the License at 20 | 21 | http://www.apache.org/licenses/LICENSE-2.0 22 | 23 | Unless required by applicable law or agreed to in writing, software 24 | distributed under the License is distributed on an "AS IS" BASIS, 25 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 26 | See the License for the specific language governing permissions and 27 | limitations under the License. 28 | 29 | Originally written by [Julien Ponge](https://julien.ponge.org/). 30 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Mingw, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | # TODO classpath? 118 | fi 119 | 120 | if [ -z "$JAVA_HOME" ]; then 121 | javaExecutable="`which javac`" 122 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 123 | # readlink(1) is not available as standard on Solaris 10. 124 | readLink=`which readlink` 125 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 126 | if $darwin ; then 127 | javaHome="`dirname \"$javaExecutable\"`" 128 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 129 | else 130 | javaExecutable="`readlink -f \"$javaExecutable\"`" 131 | fi 132 | javaHome="`dirname \"$javaExecutable\"`" 133 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 134 | JAVA_HOME="$javaHome" 135 | export JAVA_HOME 136 | fi 137 | fi 138 | fi 139 | 140 | if [ -z "$JAVACMD" ] ; then 141 | if [ -n "$JAVA_HOME" ] ; then 142 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 143 | # IBM's JDK on AIX uses strange locations for the executables 144 | JAVACMD="$JAVA_HOME/jre/sh/java" 145 | else 146 | JAVACMD="$JAVA_HOME/bin/java" 147 | fi 148 | else 149 | JAVACMD="`which java`" 150 | fi 151 | fi 152 | 153 | if [ ! -x "$JAVACMD" ] ; then 154 | echo "Error: JAVA_HOME is not defined correctly." >&2 155 | echo " We cannot execute $JAVACMD" >&2 156 | exit 1 157 | fi 158 | 159 | if [ -z "$JAVA_HOME" ] ; then 160 | echo "Warning: JAVA_HOME environment variable is not set." 161 | fi 162 | 163 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 164 | 165 | # traverses directory structure from process work directory to filesystem root 166 | # first directory with .mvn subdirectory is considered project base directory 167 | find_maven_basedir() { 168 | 169 | if [ -z "$1" ] 170 | then 171 | echo "Path not specified to find_maven_basedir" 172 | return 1 173 | fi 174 | 175 | basedir="$1" 176 | wdir="$1" 177 | while [ "$wdir" != '/' ] ; do 178 | if [ -d "$wdir"/.mvn ] ; then 179 | basedir=$wdir 180 | break 181 | fi 182 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 183 | if [ -d "${wdir}" ]; then 184 | wdir=`cd "$wdir/.."; pwd` 185 | fi 186 | # end of workaround 187 | done 188 | echo "${basedir}" 189 | } 190 | 191 | # concatenates all lines of a file 192 | concat_lines() { 193 | if [ -f "$1" ]; then 194 | echo "$(tr -s '\n' ' ' < "$1")" 195 | fi 196 | } 197 | 198 | BASE_DIR=`find_maven_basedir "$(pwd)"` 199 | if [ -z "$BASE_DIR" ]; then 200 | exit 1; 201 | fi 202 | 203 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 204 | if [ "$MVNW_VERBOSE" = true ]; then 205 | echo $MAVEN_PROJECTBASEDIR 206 | fi 207 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 208 | 209 | # For Cygwin, switch paths to Windows format before running java 210 | if $cygwin; then 211 | [ -n "$M2_HOME" ] && 212 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 213 | [ -n "$JAVA_HOME" ] && 214 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 215 | [ -n "$CLASSPATH" ] && 216 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 217 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 218 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 219 | fi 220 | 221 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 222 | 223 | exec "$JAVACMD" \ 224 | $MAVEN_OPTS \ 225 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 226 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 227 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 228 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 50 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | 121 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 122 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 123 | 124 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 125 | if ERRORLEVEL 1 goto error 126 | goto end 127 | 128 | :error 129 | set ERROR_CODE=1 130 | 131 | :end 132 | @endlocal & set ERROR_CODE=%ERROR_CODE% 133 | 134 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 135 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 136 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 137 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 138 | :skipRcPost 139 | 140 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 141 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 142 | 143 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 144 | 145 | exit /B %ERROR_CODE% 146 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 4.0.0 18 | jar 19 | 20 | 21 | io.vertx 22 | vertx5-parent 23 | 12 24 | 25 | 26 | vertx-junit5 27 | 5.1.0-SNAPSHOT 28 | 29 | Vert.x JUnit 5 support :: Core 30 | 31 | 32 | scm:git:git@github.com:eclipse-vertx/vertx-junit5.git 33 | scm:git:git@github.com:eclipse-vertx/vertx-junit5.git 34 | git@github.com:eclipse-vertx/vertx-junit5.git 35 | 36 | 37 | 38 | 5.9.3 39 | 1.0.0 40 | 3.24.2 41 | 1.2.1 42 | 43 | 44 | 45 | 46 | 47 | io.vertx 48 | vertx-dependencies 49 | ${project.version} 50 | pom 51 | import 52 | 53 | 54 | org.junit 55 | junit-bom 56 | ${junit-jupiter.version} 57 | pom 58 | import 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | io.vertx 67 | vertx-codegen-api 68 | true 69 | 70 | 71 | io.vertx 72 | vertx-codegen-json 73 | true 74 | 75 | 76 | io.vertx 77 | vertx-docgen-api 78 | true 79 | 80 | 81 | 82 | io.vertx 83 | vertx-core 84 | 85 | 86 | 87 | org.junit.jupiter 88 | junit-jupiter-api 89 | compile 90 | 91 | 92 | org.junit.jupiter 93 | junit-jupiter-params 94 | compile 95 | 96 | 97 | org.junit.jupiter 98 | junit-jupiter-engine 99 | runtime 100 | 101 | 102 | 103 | org.junit.jupiter 104 | junit-jupiter 105 | test 106 | 107 | 108 | org.junit.platform 109 | junit-platform-launcher 110 | test 111 | 112 | 113 | 114 | com.github.stefanbirkner 115 | system-lambda 116 | ${system-lamda.version} 117 | test 118 | 119 | 120 | 121 | org.assertj 122 | assertj-core 123 | ${assertj-core.version} 124 | provided 125 | true 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | maven-compiler-plugin 134 | 135 | 136 | default-compile 137 | 138 | 139 | 140 | io.vertx 141 | vertx-codegen 142 | processor 143 | 144 | 145 | io.vertx 146 | vertx-docgen-processor 147 | processor 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | maven-surefire-plugin 156 | 157 | 158 | --add-opens java.base/java.util=system.lambda 159 | programmatic 160 | 161 | 162 | 163 | 164 | 165 | 166 | maven-assembly-plugin 167 | 168 | 169 | package-docs 170 | 171 | single 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | java17-running-test-with-strong-encapsulation 182 | 183 | 17 184 | 185 | 186 | 187 | 188 | org.apache.maven.plugins 189 | maven-surefire-plugin 190 | 191 | programmatic 192 | --add-opens=java.base/java.util=ALL-UNNAMED 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | -------------------------------------------------------------------------------- /src/main/asciidoc/index.adoc: -------------------------------------------------------------------------------- 1 | = Vert.x JUnit 5 integration 2 | 3 | This module offers integration and support for writing Vert.x tests with JUnit 5. 4 | 5 | == Use it in your build 6 | 7 | To use this component, add the following dependency to the dependencies section of your build descriptor: 8 | 9 | * Maven (in your `pom.xml`): 10 | 11 | [source,xml,subs="+attributes"] 12 | ---- 13 | 14 | io.vertx 15 | vertx-junit5 16 | ${maven.version} 17 | 18 | ---- 19 | 20 | * Gradle (in your `build.gradle` file): 21 | 22 | [source,groovy,subs="+attributes"] 23 | ---- 24 | compile io.vertx:vertx-junit5:${maven.version} 25 | ---- 26 | 27 | == Why testing asynchronous code is different 28 | 29 | Testing asynchronous operations requires more tools than what a test harness like JUnit provides. 30 | Let us consider a typical Vert.x creation of a HTTP server, and put it into a JUnit test: 31 | 32 | [source,java] 33 | ---- 34 | {@link examples.Examples.ATest} 35 | ---- 36 | 37 | There are issues here since `listen` does not block as it tries to start a HTTP server asynchronously. 38 | We cannot simply assume that the server has properly started upon a `listen` invocation return. 39 | Also: 40 | 41 | 1. the callback passed to `listen` will be executed from a Vert.x event loop thread, which is different from the thread that runs the JUnit test, and 42 | 2. right after calling `listen`, the test exits and is being considered to be passed, while the HTTP server may not even have finished starting, and 43 | 3. since the `listen` callback executes on a different thread than the one executing the test, then any exception such as one thrown by a failed assertion cannot be capture by the JUnit runner. 44 | 45 | == A test context for asynchronous executions 46 | 47 | The first contribution of this module is a {@link io.vertx.junit5.VertxTestContext} object that: 48 | 49 | 1. allows waiting for operations in other threads to notify of completion, and 50 | 2. supports assertion failures to be received to mark a test as failed. 51 | 52 | Here is a very basic usage: 53 | 54 | [source,java] 55 | ---- 56 | {@link examples.Examples.BTest} 57 | ---- 58 | 59 | <1> {@link io.vertx.junit5.VertxTestContext#succeedingThenComplete} returns an asynchronous result handler that is expected to succeed and then make the test context pass. 60 | <2> {@link io.vertx.junit5.VertxTestContext#awaitCompletion} has the semantics of a `java.util.concurrent.CountDownLatch`, and returns `false` if the waiting delay expired before the test passed. 61 | <3> If the context captures a (potentially asynchronous) error, then after completion we must throw the failure exception to make the test fail. 62 | 63 | == Use any assertion library 64 | 65 | This module does not make any assumption on the assertion library that you should be using. 66 | You can use plain JUnit assertions, http://joel-costigliola.github.io/assertj/[AssertJ], etc. 67 | 68 | To make assertions in asynchronous code and make sure that {@link io.vertx.junit5.VertxTestContext} is notified of potential failures, you need to wrap them with a call to {@link io.vertx.junit5.VertxTestContext#verify}, {@link io.vertx.junit5.VertxTestContext#succeeding(io.vertx.core.Handler)}, or {@link io.vertx.junit5.VertxTestContext#failing(io.vertx.core.Handler)}: 69 | 70 | [source,java] 71 | ---- 72 | {@link examples.Examples#usingVerify} 73 | ---- 74 | 75 | The useful methods in {@link io.vertx.junit5.VertxTestContext} are the following: 76 | 77 | * {@link io.vertx.junit5.VertxTestContext#completeNow} and {@link io.vertx.junit5.VertxTestContext#failNow} to notify of a success or failure 78 | * {@link io.vertx.junit5.VertxTestContext#succeedingThenComplete} to provide `Handler>` handlers that expect a success and then completes the test context 79 | * {@link io.vertx.junit5.VertxTestContext#failingThenComplete} to provide `Handler>` handlers that expect a failure and then completes the test context 80 | * {@link io.vertx.junit5.VertxTestContext#succeeding} to provide `Handler>` handlers that expect a success and pass the result to another callback, any exception thrown from the callback is considered as a test failure 81 | * {@link io.vertx.junit5.VertxTestContext#failing} to provide `Handler>` handlers that expect a failure and pass the exception to another callback, any exception thrown from the callback is considered as a test failure 82 | * {@link io.vertx.junit5.VertxTestContext#verify} to perform assertions, any exception thrown from the code block is considered as a test failure. 83 | 84 | WARNING: Unlike `succeedingThenComplete` and `failingThenComplete`, calling `succeeding` and `failing` methods can only make a test fail (e.g., `succeeding` gets a failed asynchronous result). 85 | To make a test pass you still need to call `completeNow`, or use checkpoints as explained below. 86 | 87 | == Checkpoint when there are multiple success conditions 88 | 89 | Many tests can be marked as passed by simply calling {@link io.vertx.junit5.VertxTestContext#completeNow} at some point of the execution. 90 | That being said there are also many cases where the success of a test depends on different asynchronous parts to be validated. 91 | 92 | You can use checkpoints to flag some execution points to be passed. 93 | A {@link io.vertx.junit5.Checkpoint} can require a single flagging, or multiple flags. 94 | When all checkpoints have been flagged, then the corresponding {@link io.vertx.junit5.VertxTestContext} makes the test pass. 95 | 96 | Here is an example with checkpoints on the HTTP server being started, 10 HTTP requests having being responded, and 10 HTTP client requests having been made: 97 | 98 | [source,java] 99 | ---- 100 | {@link examples.Examples#checkpointing} 101 | ---- 102 | 103 | TIP: Checkpoints should be created only from the test case main thread, not from Vert.x asynchronous event callbacks. 104 | 105 | == Integration with JUnit 5 106 | 107 | JUnit 5 provides a different model compared to the previous versions. 108 | 109 | === Test methods 110 | 111 | The Vert.x integration is primarily done using the {@link io.vertx.junit5.VertxExtension} class, and using test parameter injection of `Vertx` and `VertxTestContext` instances: 112 | 113 | [source,java] 114 | ---- 115 | {@link examples.Examples.CTest.SomeTest} 116 | ---- 117 | 118 | NOTE: The `Vertx` instance is not clustered and has the default configuration. 119 | If you need something else then just don't use injection on that parameter and prepare a `Vertx` object by yourself. 120 | 121 | The test is automatically wrapped around the {@link io.vertx.junit5.VertxTestContext} instance lifecycle, so you don't need to insert {@link io.vertx.junit5.VertxTestContext#awaitCompletion} calls yourself: 122 | 123 | [source,java] 124 | ---- 125 | {@link examples.Examples.DTest.SomeTest} 126 | ---- 127 | 128 | You can use it with standard JUnit annotations such as `@RepeatedTest` or lifecycle callbacks annotations: 129 | 130 | [source,java] 131 | ---- 132 | {@link examples.Examples.ETest.SomeTest} 133 | ---- 134 | 135 | It is also possible to customize the default {@link io.vertx.junit5.VertxTestContext} timeout using the {@link io.vertx.junit5.Timeout} annotation either on test classes or methods: 136 | 137 | [source,java] 138 | ---- 139 | {@link examples.Examples.FTest.SomeTest} 140 | ---- 141 | 142 | === Lifecycle methods 143 | 144 | JUnit 5 provides several user-defined lifecycle methods annotated with `@BeforeAll`, `@BeforeEach`, `@AfterEach` and `@AfterAll`. 145 | 146 | These methods can request the injection of `Vertx` instances. 147 | By doing so, they are likely to perform asynchronous operations with the `Vertx` instance, so they can request the injection of a `VertxTestContext` instance to ensure that the JUnit runner waits for them to complete, and report possible errors. 148 | 149 | Here is an example: 150 | 151 | [source,java] 152 | ---- 153 | {@link examples.LifecycleExampleTest} 154 | ---- 155 | 156 | ==== Scope of `VertxTestContext` objects 157 | 158 | Since these objects help waiting for asynchronous operations to complete, a new instance is created for any `@Test`, `@BeforeAll`, `@BeforeEach`, `@AfterEach` and `@AfterAll` method. 159 | 160 | ==== Scope of `Vertx` objects 161 | 162 | The scope of a `Vertx` object depends on which lifecycle method in the http://junit.org/junit5/docs/current/user-guide/#extensions-execution-order[JUnit relative execution order] first required a new instance to be created. 163 | Generally-speaking, we respect the JUnit extension scoping rules, but here are the specifications. 164 | 165 | 1. If a parent test context already had a `Vertx` instance, it is being reused in children extension test contexts. 166 | 2. Injecting in a `@BeforeAll` method creates a new instance that is being shared for injection in all subsequent test and lifecycle methods. 167 | 3. Injecting in a `@BeforeEach` with no parent context or previous `@BeforeAll` injection creates a new instance shared with the corresponding test and `AfterEach` method(s). 168 | 4. When no instance exists before running a test method, an instance is created for that test (and only for that test). 169 | 170 | ==== Configuring `Vertx` instances 171 | 172 | By default, the `Vertx` objects get created with `Vertx.vertx()`, using the default settings for `Vertx`. 173 | However, you have the ability to configure `VertxOptions` to suit your needs. 174 | A typical use case would be "extending blocking timeout warning for debugging". 175 | To configure the `Vertx` object you must: 176 | 177 | 1. create a json file with the `VertxOptions` in https://vertx.io/docs/apidocs/io/vertx/core/VertxOptions.html#VertxOptions-io.vertx.core.json.JsonObject-[json format] 178 | 2. create an environment variable `VERTX_PARAMETER_FILENAME`, or a system property `vertx.parameter.filename`, pointing to that file 179 | 180 | TIP: The environment variable value takes precedence over the system property value, if both are present. 181 | 182 | Example file content for extended timeouts: 183 | 184 | [source,json] 185 | { 186 | "blockedThreadCheckInterval" : 5, 187 | "blockedThreadCheckIntervalUnit" : "MINUTES", 188 | "maxEventLoopExecuteTime" : 360, 189 | "maxEventLoopExecuteTimeUnit" : "SECONDS" 190 | } 191 | 192 | With these conditions met, the `Vertx` object will be created with the configured options 193 | 194 | ==== Closing and removal of `Vertx` objects 195 | 196 | Injected `Vertx` objects are being automatically closed and removed from their corresponding scopes. 197 | 198 | For instance if a `Vertx` object is created for the scope of a test method, it is being closed after the test completes. 199 | Similarly, when it is being created by a `@BeforeEach` method, it is being closed after possible `@AfterEach` methods have completed. 200 | 201 | == Support for additional parameter types 202 | 203 | The Vert.x JUnit 5 extension is extensible: you can add more types through the 204 | {@link io.vertx.junit5.VertxExtensionParameterProvider} service provider interface. 205 | 206 | If you use RxJava, instead of `io.vertx.core.Vertx` you can inject: 207 | 208 | * `io.vertx.rxjava3.core.Vertx`, or 209 | * `io.vertx.reactivex.core.Vertx`, or 210 | * `io.vertx.rxjava.core.Vertx`. 211 | 212 | To do so, add the corresponding library to your project: 213 | 214 | * `io.vertx:vertx-junit5-rx-java3`, or 215 | * `io.vertx:vertx-junit5-rx-java2`, or 216 | * `io.vertx:vertx-junit5-rx-java`. 217 | 218 | On Reactiverse you can find a growing collection of extensions for `vertx-junit5` that integrates with Vert.x stack in the `reactiverse-junit5-extensions` project: 219 | https://github.com/reactiverse/reactiverse-junit5-extensions. 220 | 221 | == Parameter ordering 222 | 223 | It may be the case that a parameter type has to be placed before another parameter. 224 | For instance the Web Client support in the `reactiverse-junit5-extensions` project requires that the `Vertx` argument is before the `WebClient` argument. 225 | This is because the `Vertx` instance needs to exist to create the `WebClient`. 226 | 227 | It is expected that parameter providers throw meaningful exceptions to let users know of possible ordering constraints. 228 | 229 | In any case it is a good idea to have the `Vertx` parameter first, and the next parameters in the order of what you'd need to create them manually. 230 | 231 | == Parameterized tests with `@MethodSource` 232 | 233 | You can use parameterized tests with `@MethodSource` with vertx-junit5. Therefore you need to declare the method source parameters before the vertx test parameters in the method definition. 234 | 235 | [source,java] 236 | ---- 237 | {@link examples.Examples.PTest.SomeTest} 238 | ---- 239 | 240 | The same holds for the other `ArgumentSources`. 241 | See the section `Formal Parameter List` in the API doc of 242 | https://junit.org/junit5/docs/current/api/org.junit.jupiter.params/org/junit/jupiter/params/ParameterizedTest.html[ParameterizedTest] 243 | 244 | == Running tests on a Vert.x context 245 | 246 | By default the thread invoking the test methods is the JUnit thread. 247 | The {@link io.vertx.junit5.RunTestOnContext} extension can be used to alter this behavior by running test methods on a Vert.x event-loop thread. 248 | 249 | CAUTION: Keep in mind that you must not block the event loop when using this extension. 250 | 251 | For this purpose, the extension needs a {@link io.vertx.core.Vertx} instance. 252 | By default, it creates one automatically but you can provide options for configuration or a supplier method. 253 | 254 | The {@link io.vertx.core.Vertx} instance can be retrieved when the test is running. 255 | 256 | [source,java] 257 | ---- 258 | {@link examples.RunTestOnContextExampleTest} 259 | ---- 260 | 261 | When used as a `@RegisterExtension` instance field, a new {@link io.vertx.core.Vertx} object and {@link io.vertx.core.Context} are created for each tested method. 262 | `@BeforeEach` and `@AfterEach` methods are executed on this context. 263 | 264 | When used as a `@RegisterExtension` static field, a single {@link io.vertx.core.Vertx} object and {@link io.vertx.core.Context} are created for all the tested methods. 265 | `@BeforeAll` and `@AfterAll` methods are executed on this context too. 266 | -------------------------------------------------------------------------------- /src/main/java/examples/Examples.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 Red Hat, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package examples; 18 | 19 | import io.vertx.core.VerticleBase; 20 | import io.vertx.core.Vertx; 21 | import io.vertx.core.http.HttpClient; 22 | import io.vertx.core.http.HttpClientResponse; 23 | import io.vertx.core.http.HttpMethod; 24 | import io.vertx.junit5.Checkpoint; 25 | import io.vertx.junit5.Timeout; 26 | import io.vertx.junit5.VertxExtension; 27 | import io.vertx.junit5.VertxTestContext; 28 | import org.junit.jupiter.api.BeforeEach; 29 | import org.junit.jupiter.api.Nested; 30 | import org.junit.jupiter.api.RepeatedTest; 31 | import org.junit.jupiter.api.Test; 32 | import org.junit.jupiter.api.extension.ExtendWith; 33 | import org.junit.jupiter.params.ParameterizedTest; 34 | import org.junit.jupiter.params.provider.Arguments; 35 | import org.junit.jupiter.params.provider.MethodSource; 36 | 37 | import java.util.concurrent.TimeUnit; 38 | import java.util.stream.Stream; 39 | 40 | import static org.assertj.core.api.Assertions.assertThat; 41 | 42 | /** 43 | * @author Julien Ponge 44 | */ 45 | @ExtendWith(VertxExtension.class) 46 | public class Examples { 47 | 48 | HttpClient client; 49 | 50 | class ATest { 51 | Vertx vertx = Vertx.vertx(); 52 | 53 | @Test 54 | void start_server() { 55 | vertx.createHttpServer() 56 | .requestHandler(req -> req.response().end("Ok")) 57 | .listen(16969).onComplete(ar -> { 58 | // (we can check here if the server started or not) 59 | }); 60 | } 61 | } 62 | 63 | @Nested 64 | class ATestNested extends ATest { 65 | @BeforeEach 66 | void setVertx(Vertx vertx) { 67 | this.vertx = vertx; 68 | } 69 | } 70 | 71 | class BTest { 72 | Vertx vertx = Vertx.vertx(); 73 | 74 | @Test 75 | void start_http_server() throws Throwable { 76 | VertxTestContext testContext = new VertxTestContext(); 77 | 78 | vertx.createHttpServer() 79 | .requestHandler(req -> req.response().end()) 80 | .listen(16969) 81 | .onComplete(testContext.succeedingThenComplete()); // <1> 82 | 83 | assertThat(testContext.awaitCompletion(5, TimeUnit.SECONDS)).isTrue(); // <2> 84 | if (testContext.failed()) { // <3> 85 | throw testContext.causeOfFailure(); 86 | } 87 | } 88 | } 89 | 90 | @Nested 91 | class BTestNested extends BTest { 92 | @BeforeEach 93 | void setVertx(Vertx vertx) { 94 | this.vertx = vertx; 95 | } 96 | } 97 | 98 | @BeforeEach 99 | void startPlopServer(Vertx vertx, VertxTestContext testContext) { 100 | vertx.createHttpServer() 101 | .requestHandler(request -> request.response().end("Plop")) 102 | .listen(8080).onComplete(testContext.succeedingThenComplete()); 103 | } 104 | 105 | @Test 106 | public void usingVerify(Vertx vertx, VertxTestContext testContext) { 107 | client = vertx.createHttpClient(); 108 | 109 | client.request(HttpMethod.GET, 8080, "localhost", "/") 110 | .compose(req -> req.send().compose(HttpClientResponse::body)) 111 | .onComplete(testContext.succeeding(buffer -> testContext.verify(() -> { 112 | assertThat(buffer.toString()).isEqualTo("Plop"); 113 | testContext.completeNow(); 114 | }))); 115 | } 116 | 117 | @Test 118 | public void checkpointing(Vertx vertx, VertxTestContext testContext) { 119 | Checkpoint serverStarted = testContext.checkpoint(); 120 | Checkpoint requestsServed = testContext.checkpoint(10); 121 | Checkpoint responsesReceived = testContext.checkpoint(10); 122 | 123 | vertx.createHttpServer() 124 | .requestHandler(req -> { 125 | req.response().end("Ok"); 126 | requestsServed.flag(); 127 | }) 128 | .listen(8888) 129 | .onComplete(testContext.succeeding(httpServer -> { 130 | serverStarted.flag(); 131 | 132 | client = vertx.createHttpClient(); 133 | for (int i = 0; i < 10; i++) { 134 | client.request(HttpMethod.GET, 8888, "localhost", "/") 135 | .compose(req -> req.send().compose(HttpClientResponse::body)) 136 | .onComplete(testContext.succeeding(buffer -> testContext.verify(() -> { 137 | assertThat(buffer.toString()).isEqualTo("Ok"); 138 | responsesReceived.flag(); 139 | }))); 140 | } 141 | })); 142 | } 143 | 144 | class CTest { 145 | 146 | @ExtendWith(VertxExtension.class) 147 | class SomeTest { 148 | 149 | @Test 150 | void some_test(Vertx vertx, VertxTestContext testContext) { 151 | // (...) 152 | } 153 | } 154 | } 155 | 156 | class HttpServerVerticle extends VerticleBase { 157 | } 158 | 159 | class DTest { 160 | 161 | @ExtendWith(VertxExtension.class) 162 | class SomeTest { 163 | 164 | HttpClient client; 165 | 166 | @Test 167 | void http_server_check_response(Vertx vertx, VertxTestContext testContext) { 168 | vertx.deployVerticle(new HttpServerVerticle()).onComplete(testContext.succeeding(id -> { 169 | client = vertx.createHttpClient(); 170 | client.request(HttpMethod.GET, 8080, "localhost", "/") 171 | .compose(req -> req.send().compose(HttpClientResponse::body)) 172 | .onComplete(testContext.succeeding(buffer -> testContext.verify(() -> { 173 | assertThat(buffer.toString()).isEqualTo("Plop"); 174 | testContext.completeNow(); 175 | }))); 176 | })); 177 | } 178 | } 179 | } 180 | 181 | @Nested 182 | class DTestNested extends DTest { 183 | @Nested 184 | class SomeTestNested extends DTest.SomeTest { 185 | } 186 | } 187 | 188 | class ETest { 189 | 190 | @ExtendWith(VertxExtension.class) 191 | class SomeTest { 192 | 193 | // Deploy the verticle and execute the test methods when the verticle 194 | // is successfully deployed 195 | @BeforeEach 196 | void deploy_verticle(Vertx vertx, VertxTestContext testContext) { 197 | vertx.deployVerticle(new HttpServerVerticle()).onComplete(testContext.succeedingThenComplete()); 198 | } 199 | 200 | HttpClient client; 201 | 202 | // Repeat this test 3 times 203 | @RepeatedTest(3) 204 | void http_server_check_response(Vertx vertx, VertxTestContext testContext) { 205 | client = vertx.createHttpClient(); 206 | client.request(HttpMethod.GET, 8080, "localhost", "/") 207 | .compose(req -> req.send().compose(HttpClientResponse::body)) 208 | .onComplete(testContext.succeeding(buffer -> testContext.verify(() -> { 209 | assertThat(buffer.toString()).isEqualTo("Plop"); 210 | testContext.completeNow(); 211 | }))); 212 | } 213 | } 214 | } 215 | 216 | @Nested 217 | class ETestNested extends ETest { 218 | @Nested 219 | class SomeTestNested extends ETest.SomeTest { 220 | } 221 | } 222 | 223 | class FTest { 224 | 225 | @ExtendWith(VertxExtension.class) 226 | class SomeTest { 227 | 228 | @Test 229 | @Timeout(value = 10, timeUnit = TimeUnit.SECONDS) 230 | void some_test(Vertx vertx, VertxTestContext context) { 231 | // (...) 232 | } 233 | } 234 | } 235 | 236 | static class PTest { 237 | 238 | @ExtendWith(VertxExtension.class) 239 | static class SomeTest { 240 | 241 | static Stream testData() { 242 | return Stream.of( 243 | Arguments.of("complex object1", 4), 244 | Arguments.of("complex object2", 0) 245 | ); 246 | } 247 | 248 | @ParameterizedTest 249 | @MethodSource("testData") 250 | void test2(String obj, int count, Vertx vertx, VertxTestContext testContext) { 251 | // your test code 252 | testContext.completeNow(); 253 | } 254 | } 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /src/main/java/examples/LifecycleExampleTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 Red Hat, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package examples; 18 | 19 | import io.vertx.core.VerticleBase; 20 | import io.vertx.core.Vertx; 21 | import io.vertx.junit5.VertxExtension; 22 | import io.vertx.junit5.VertxTestContext; 23 | import org.junit.jupiter.api.AfterEach; 24 | import org.junit.jupiter.api.BeforeEach; 25 | import org.junit.jupiter.api.DisplayName; 26 | import org.junit.jupiter.api.Test; 27 | import org.junit.jupiter.api.extension.ExtendWith; 28 | 29 | import static org.assertj.core.api.Assertions.assertThat; 30 | 31 | /** 32 | * @author Julien Ponge 33 | */ 34 | @ExtendWith(VertxExtension.class) 35 | class LifecycleExampleTest { 36 | 37 | @BeforeEach 38 | @DisplayName("Deploy a verticle") 39 | void prepare(Vertx vertx, VertxTestContext testContext) { 40 | vertx.deployVerticle(new SomeVerticle()).onComplete(testContext.succeedingThenComplete()); 41 | } 42 | 43 | @Test 44 | @DisplayName("A first test") 45 | void foo(Vertx vertx, VertxTestContext testContext) { 46 | // (...) 47 | testContext.completeNow(); 48 | } 49 | 50 | @Test 51 | @DisplayName("A second test") 52 | void bar(Vertx vertx, VertxTestContext testContext) { 53 | // (...) 54 | testContext.completeNow(); 55 | } 56 | 57 | @AfterEach 58 | @DisplayName("Check that the verticle is still there") 59 | void lastChecks(Vertx vertx) { 60 | assertThat(vertx.deploymentIDs()) 61 | .isNotEmpty() 62 | .hasSize(1); 63 | } 64 | } 65 | 66 | class SomeVerticle extends VerticleBase { 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/examples/RunTestOnContextExampleTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Red Hat, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package examples; 18 | 19 | import io.vertx.core.Vertx; 20 | import io.vertx.junit5.RunTestOnContext; 21 | import io.vertx.junit5.VertxExtension; 22 | import io.vertx.junit5.VertxTestContext; 23 | import org.junit.jupiter.api.AfterEach; 24 | import org.junit.jupiter.api.BeforeEach; 25 | import org.junit.jupiter.api.Test; 26 | import org.junit.jupiter.api.extension.ExtendWith; 27 | import org.junit.jupiter.api.extension.RegisterExtension; 28 | 29 | @ExtendWith(VertxExtension.class) 30 | class RunTestOnContextExampleTest { 31 | 32 | @RegisterExtension 33 | RunTestOnContext rtoc = new RunTestOnContext(); 34 | 35 | Vertx vertx; 36 | 37 | @BeforeEach 38 | void prepare(VertxTestContext testContext) { 39 | vertx = rtoc.vertx(); 40 | // Prepare something on a Vert.x event-loop thread 41 | // The thread changes with each test instance 42 | testContext.completeNow(); 43 | } 44 | 45 | @Test 46 | void foo(VertxTestContext testContext) { 47 | // Test something on the same Vert.x event-loop thread 48 | // that called prepare 49 | testContext.completeNow(); 50 | } 51 | 52 | @AfterEach 53 | void cleanUp(VertxTestContext testContext) { 54 | // Clean things up on the same Vert.x event-loop thread 55 | // that called prepare and foo 56 | testContext.completeNow(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/examples/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 Red Hat, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * @author Julien Ponge 19 | */ 20 | @Source 21 | package examples; 22 | 23 | import io.vertx.docgen.Source; 24 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/junit5/Checkpoint.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 Red Hat, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.vertx.junit5; 18 | 19 | /** 20 | * A test completion checkpoint, flagging it advances towards the test context completion. 21 | * 22 | * @author Julien Ponge 23 | * @see VertxTestContext 24 | */ 25 | public interface Checkpoint { 26 | 27 | /** 28 | * Flags the checkpoint. 29 | */ 30 | void flag(); 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/junit5/CountingCheckpoint.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 Red Hat, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.vertx.junit5; 18 | 19 | import java.util.Objects; 20 | import java.util.function.Consumer; 21 | 22 | /** 23 | * Checkpoints that count the number of flag invocations. 24 | * 25 | * @author Julien Ponge 26 | */ 27 | public final class CountingCheckpoint implements Checkpoint { 28 | 29 | private final Consumer satisfactionTrigger; 30 | private final Consumer overuseTrigger; 31 | private final int requiredNumberOfPasses; 32 | private final StackTraceElement creationCallSite; 33 | 34 | private int numberOfPasses = 0; 35 | private boolean satisfied = false; 36 | 37 | public static CountingCheckpoint laxCountingCheckpoint(Consumer satisfactionTrigger, int requiredNumberOfPasses) { 38 | return new CountingCheckpoint(satisfactionTrigger, null, requiredNumberOfPasses); 39 | } 40 | 41 | public static CountingCheckpoint strictCountingCheckpoint(Consumer satisfactionTrigger, Consumer overuseTrigger, int requiredNumberOfPasses) { 42 | Objects.requireNonNull(overuseTrigger); 43 | return new CountingCheckpoint(satisfactionTrigger, overuseTrigger, requiredNumberOfPasses); 44 | } 45 | 46 | private CountingCheckpoint(Consumer satisfactionTrigger, Consumer overuseTrigger, int requiredNumberOfPasses) { 47 | Objects.requireNonNull(satisfactionTrigger); 48 | if (requiredNumberOfPasses <= 0) { 49 | throw new IllegalArgumentException("A checkpoint needs at least 1 pass"); 50 | } 51 | this.creationCallSite = findCallSite(); 52 | this.satisfactionTrigger = satisfactionTrigger; 53 | this.overuseTrigger = overuseTrigger; 54 | this.requiredNumberOfPasses = requiredNumberOfPasses; 55 | } 56 | 57 | private StackTraceElement findCallSite() { 58 | StackTraceElement[] stackTrace = new Exception().getStackTrace(); 59 | for (int i = stackTrace.length - 1; i >= 0; i--) { 60 | if (stackTrace[i].getClassName().equals(VertxTestContext.class.getName())) { 61 | return stackTrace[i + 1]; 62 | } 63 | } 64 | return stackTrace[1]; // This can only happen from direct usage of CountingCheckpoint in tests, so the value is irrelevant 65 | } 66 | 67 | @Override 68 | public void flag() { 69 | boolean callSatisfactionTrigger = false; 70 | boolean callOveruseTrigger = false; 71 | synchronized (this) { 72 | if (satisfied) { 73 | callOveruseTrigger = true; 74 | } else { 75 | numberOfPasses = numberOfPasses + 1; 76 | if (numberOfPasses == requiredNumberOfPasses) { 77 | callSatisfactionTrigger = true; 78 | satisfied = true; 79 | } 80 | } 81 | } 82 | if (callSatisfactionTrigger) { 83 | satisfactionTrigger.accept(this); 84 | } else if (callOveruseTrigger && overuseTrigger != null) { 85 | overuseTrigger.accept(new IllegalStateException("Strict checkpoint flagged too many times")); 86 | } 87 | } 88 | 89 | public boolean satisfied() { 90 | return this.satisfied; 91 | } 92 | 93 | public StackTraceElement creationCallSite() { 94 | return creationCallSite; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/junit5/ParameterClosingConsumer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Red Hat, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.vertx.junit5; 18 | 19 | /** 20 | * A consumer to close extension parameters, like closing a Vert.x context, a web client, etc. 21 | *

22 | * This is useful for parameter providers. 23 | * 24 | * @param Parameter type 25 | * @author Julien Ponge 26 | */ 27 | @FunctionalInterface 28 | public interface ParameterClosingConsumer { 29 | void accept(T obj) throws Exception; 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/junit5/RunTestOnContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Red Hat, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.vertx.junit5; 18 | 19 | import io.vertx.core.*; 20 | import io.vertx.core.internal.VertxInternal; 21 | import org.junit.jupiter.api.extension.*; 22 | 23 | import java.lang.reflect.Method; 24 | import java.util.concurrent.CompletableFuture; 25 | import java.util.concurrent.ExecutionException; 26 | import java.util.function.Function; 27 | import java.util.function.Supplier; 28 | 29 | /** 30 | * An extension that runs tests on a Vert.x context. 31 | *

32 | * When used as a {@link RegisterExtension} instance field, a new {@link Vertx} object and {@link Context} are created for each tested method. 33 | * {@link org.junit.jupiter.api.BeforeEach} and {@link org.junit.jupiter.api.AfterEach} methods are executed on this context. 34 | *

35 | * When used as a {@link RegisterExtension} static field, a single {@link Vertx} object and {@link Context} are created for all the tested methods. 36 | * {@link org.junit.jupiter.api.BeforeAll} and {@link org.junit.jupiter.api.AfterAll} methods are executed on this context too. 37 | */ 38 | public class RunTestOnContext implements BeforeAllCallback, InvocationInterceptor, AfterEachCallback, AfterAllCallback { 39 | 40 | private volatile boolean staticExtension; 41 | 42 | private volatile Vertx vertx; 43 | private volatile Context context; 44 | 45 | private final Supplier> supplier; 46 | private final Function> shutdown; 47 | 48 | /** 49 | * Create an instance of this extension that builds a {@link Vertx} object using default options. 50 | */ 51 | public RunTestOnContext() { 52 | this(new VertxOptions(), false); 53 | } 54 | 55 | /** 56 | * Create an instance of this extension that builds a {@link Vertx} object using the specified {@code options}. 57 | *

58 | * When the options hold a {@link io.vertx.core.spi.cluster.ClusterManager} instance, a clustered {@link Vertx} object is created. 59 | * 60 | * @param options the vertx options 61 | */ 62 | public RunTestOnContext(VertxOptions options, boolean clustered) { 63 | this(() -> clustered ? Vertx.clusteredVertx(options) : Future.succeededFuture(Vertx.vertx(options))); 64 | } 65 | 66 | /** 67 | * Create an instance of this extension that gets a {@link Vertx} object using the specified asynchronous {@code supplier}. 68 | * 69 | * @param supplier the asynchronous supplier 70 | */ 71 | public RunTestOnContext(Supplier> supplier) { 72 | this(supplier, Vertx::close); 73 | } 74 | 75 | /** 76 | * Create an instance of this extension that gets a {@link Vertx} object using the specified asynchronous {@code supplier}. 77 | * The asynchronous {@code shutdown} function is invoked when the {@link Vertx} object is no longer needed. 78 | * 79 | * @param supplier the asynchronous supplier 80 | * @param shutdown the asynchronous shutdown function 81 | */ 82 | public RunTestOnContext(Supplier> supplier, Function> shutdown) { 83 | this.supplier = supplier; 84 | this.shutdown = shutdown; 85 | } 86 | 87 | /** 88 | * @return the current Vert.x instance 89 | */ 90 | public Vertx vertx() { 91 | return vertx; 92 | } 93 | 94 | @Override 95 | public void beforeAll(ExtensionContext context) { 96 | staticExtension = true; 97 | } 98 | 99 | @Override 100 | public void interceptBeforeAllMethod(Invocation invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { 101 | runOnContext(invocation); 102 | } 103 | 104 | @Override 105 | public void interceptBeforeEachMethod(Invocation invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { 106 | runOnContext(invocation); 107 | } 108 | 109 | @Override 110 | public void interceptTestMethod(Invocation invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { 111 | runOnContext(invocation); 112 | } 113 | 114 | @Override 115 | public T interceptTestFactoryMethod(Invocation invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { 116 | return runOnContext(invocation); 117 | } 118 | 119 | @Override 120 | public void interceptTestTemplateMethod(Invocation invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { 121 | runOnContext(invocation); 122 | } 123 | 124 | @Override 125 | public void interceptDynamicTest(Invocation invocation, ExtensionContext extensionContext) throws Throwable { 126 | runOnContext(invocation); 127 | } 128 | 129 | @Override 130 | public void interceptAfterEachMethod(Invocation invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { 131 | runOnContext(invocation); 132 | } 133 | 134 | @Override 135 | public void interceptAfterAllMethod(Invocation invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { 136 | runOnContext(invocation); 137 | } 138 | 139 | private T runOnContext(Invocation invocation) throws Throwable { 140 | Future vertxFuture; 141 | if (vertx != null) { 142 | vertxFuture = Future.succeededFuture(vertx); 143 | } else { 144 | vertxFuture = supplier.get().onSuccess(vertx -> { 145 | this.vertx = vertx; 146 | context = ((VertxInternal) vertx).createEventLoopContext(); 147 | }); 148 | } 149 | CompletableFuture cf = vertxFuture 150 | .compose(ignore -> { 151 | Promise promise = Promise.promise(); 152 | context.runOnContext(v -> { 153 | try { 154 | promise.complete(invocation.proceed()); 155 | } catch (Throwable e) { 156 | promise.fail(e); 157 | } 158 | }); 159 | return promise.future(); 160 | }) 161 | .toCompletionStage() 162 | .toCompletableFuture(); 163 | try { 164 | return cf.get(); 165 | } catch (InterruptedException e) { 166 | Thread.currentThread().interrupt(); 167 | throw e; 168 | } catch (ExecutionException e) { 169 | throw e.getCause(); 170 | } 171 | } 172 | 173 | @Override 174 | public void afterEach(ExtensionContext context) throws Exception { 175 | if (staticExtension) { 176 | return; 177 | } 178 | cleanUp(); 179 | } 180 | 181 | private void cleanUp() throws Exception { 182 | context = null; 183 | if (vertx == null) { 184 | return; 185 | } 186 | Future closeFuture = shutdown.apply(vertx); 187 | vertx = null; 188 | try { 189 | closeFuture.toCompletionStage().toCompletableFuture().get(); 190 | } catch (InterruptedException e) { 191 | Thread.currentThread().interrupt(); 192 | throw e; 193 | } catch (ExecutionException e) { 194 | Throwable cause = e.getCause(); 195 | if (cause instanceof Error) { 196 | throw (Error) cause; 197 | } 198 | if (cause instanceof Exception) { 199 | throw (Exception) cause; 200 | } 201 | throw new RuntimeException(cause); 202 | } 203 | } 204 | 205 | @Override 206 | public void afterAll(ExtensionContext context) throws Exception { 207 | cleanUp(); 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/junit5/ScopedObject.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Red Hat, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.vertx.junit5; 18 | 19 | import org.junit.jupiter.api.extension.ExtensionContext; 20 | 21 | import java.util.Objects; 22 | import java.util.function.Supplier; 23 | 24 | /** 25 | * A parameter as an object with a scope and that can be closed when the scope exits. 26 | *

27 | * This is useful for parameter providers. 28 | * 29 | * @param Parameter type 30 | * @author Julien Ponge 31 | */ 32 | public class ScopedObject implements Supplier, ExtensionContext.Store.CloseableResource { 33 | 34 | private T object; 35 | private final ParameterClosingConsumer cleaner; 36 | 37 | ScopedObject(T object, ParameterClosingConsumer cleaner) { 38 | Objects.requireNonNull(object, "The object cannot be null"); 39 | this.object = object; 40 | this.cleaner = cleaner; 41 | } 42 | 43 | @Override 44 | public void close() throws Throwable { 45 | cleaner.accept(object); 46 | } 47 | 48 | @Override 49 | public T get() { 50 | return object; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/junit5/Timeout.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 Red Hat, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.vertx.junit5; 18 | 19 | import java.lang.annotation.ElementType; 20 | import java.lang.annotation.Retention; 21 | import java.lang.annotation.RetentionPolicy; 22 | import java.lang.annotation.Target; 23 | import java.util.concurrent.TimeUnit; 24 | 25 | /** 26 | * Specify how long {@link VertxTestContext#awaitCompletion(long, TimeUnit)} waits before timing out. 27 | *

28 | * This annotation works on both test methods and test classes. 29 | * 30 | * @author Julien Ponge 31 | * @see VertxTestContext 32 | * @see org.junit.jupiter.api.Test 33 | * @see org.junit.jupiter.api.extension.ExtendWith 34 | */ 35 | @Retention(RetentionPolicy.RUNTIME) 36 | @Target({ElementType.METHOD, ElementType.TYPE}) 37 | public @interface Timeout { 38 | int value(); 39 | 40 | TimeUnit timeUnit() default TimeUnit.MILLISECONDS; 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/junit5/VertxExtension.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation 3 | * 4 | * This program and the accompanying materials are made available under the 5 | * terms of the Eclipse Public License 2.0 which is available at 6 | * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | * which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | * 9 | * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | */ 11 | 12 | package io.vertx.junit5; 13 | 14 | import io.vertx.core.Vertx; 15 | import org.junit.jupiter.api.Nested; 16 | import org.junit.jupiter.api.extension.*; 17 | import org.junit.jupiter.api.extension.ExtensionContext.Namespace; 18 | import org.junit.jupiter.api.extension.ExtensionContext.Store; 19 | 20 | import java.lang.reflect.Method; 21 | import java.util.ArrayList; 22 | import java.util.HashMap; 23 | import java.util.Optional; 24 | import java.util.ServiceLoader; 25 | import java.util.concurrent.TimeUnit; 26 | import java.util.concurrent.TimeoutException; 27 | import java.util.function.Supplier; 28 | import java.util.stream.Collectors; 29 | 30 | /** 31 | * JUnit 5 Vert.x extension that allows parameter injection as well as an automatic lifecycle on the {@link VertxTestContext} instance. 32 | *

33 | * The following types can be injected: 34 | *

    35 | *
  • {@link Vertx}
  • 36 | *
  • {@link VertxTestContext}
  • 37 | *
  • {@code io.vertx.rxjava.core.Vertx}
  • 38 | *
  • {@code io.vertx.reactivex.core.Vertx}
  • 39 | *
  • {@code io.vertx.rxjava3.core.Vertx}
  • 40 | *
41 | * 42 | * @author Julien Ponge 43 | */ 44 | public final class VertxExtension implements ParameterResolver, InvocationInterceptor { 45 | 46 | /** 47 | * Default timeout. 48 | */ 49 | public static final int DEFAULT_TIMEOUT_DURATION = 30; 50 | 51 | /** 52 | * Default timeout unit. 53 | */ 54 | public static final TimeUnit DEFAULT_TIMEOUT_UNIT = TimeUnit.SECONDS; 55 | 56 | /** 57 | * Key for all {@link Vertx} instances, including what shims like RxJava should use. 58 | */ 59 | public static final String VERTX_INSTANCE_KEY = "Vertx"; 60 | 61 | private static final String TEST_CONTEXT_KEY = "VertxTestContext"; 62 | 63 | private static class ContextList extends ArrayList { 64 | /* 65 | * There may be concurrent test contexts to join at a point of time because it is allowed to have several 66 | * user-defined lifecycle event handles (e.g., @BeforeEach, etc). 67 | */ 68 | } 69 | 70 | private final HashMap, VertxExtensionParameterProvider> parameterProviders = new HashMap<>(); 71 | 72 | public VertxExtension() { 73 | for (VertxExtensionParameterProvider parameterProvider : ServiceLoader.load(VertxExtensionParameterProvider.class)) { 74 | parameterProviders.put(parameterProvider.type(), parameterProvider); 75 | } 76 | } 77 | 78 | @Override 79 | public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { 80 | return parameterProviders.containsKey(parameterType(parameterContext)); 81 | } 82 | 83 | private Class parameterType(ParameterContext parameterContext) { 84 | return parameterContext.getParameter().getType(); 85 | } 86 | 87 | @Override 88 | public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { 89 | Class type = parameterType(parameterContext); 90 | VertxExtensionParameterProvider parameterProvider = parameterProviders.get(type); 91 | 92 | if (type.equals(VertxTestContext.class)) { 93 | return newTestContext(extensionContext); 94 | } 95 | 96 | if (extensionContext.getParent().isPresent()) { 97 | Store parentStore = store(extensionContext.getParent().get()); 98 | if (parentStore.get(parameterProvider.key()) != null) { 99 | return unpack(parentStore.get(parameterProvider.key())); 100 | } 101 | } 102 | 103 | Store store = store(extensionContext); 104 | return unpack(store.getOrComputeIfAbsent(parameterProvider.key(), key -> new ScopedObject( 105 | parameterProvider.newInstance(extensionContext, parameterContext), 106 | parameterProvider.parameterClosingConsumer()))); 107 | } 108 | 109 | private static Object unpack(Object object) { 110 | if (object instanceof Supplier) { 111 | return ((Supplier) object).get(); 112 | } 113 | return object; 114 | } 115 | 116 | private VertxTestContext newTestContext(ExtensionContext extensionContext) { 117 | Store store = store(extensionContext); 118 | ContextList contexts = (ContextList) store.getOrComputeIfAbsent(TEST_CONTEXT_KEY, key -> new ContextList()); 119 | VertxTestContext newTestContext = new VertxTestContext(); 120 | contexts.add(newTestContext); 121 | return newTestContext; 122 | } 123 | 124 | private Store store(ExtensionContext extensionContext) { 125 | return extensionContext.getStore(Namespace.GLOBAL); 126 | } 127 | 128 | @Override 129 | public void interceptBeforeAllMethod(Invocation invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { 130 | invocation.proceed(); 131 | joinActiveTestContexts(extensionContext); 132 | } 133 | 134 | @Override 135 | public void interceptAfterAllMethod(Invocation invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { 136 | invocation.proceed(); 137 | joinActiveTestContexts(extensionContext); 138 | } 139 | 140 | @Override 141 | public void interceptTestMethod(Invocation invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { 142 | invocation.proceed(); 143 | joinActiveTestContexts(extensionContext); 144 | } 145 | 146 | @Override 147 | public void interceptBeforeEachMethod(Invocation invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { 148 | invocation.proceed(); 149 | joinActiveTestContexts(extensionContext); 150 | } 151 | 152 | @Override 153 | public void interceptTestTemplateMethod(Invocation invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { 154 | invocation.proceed(); 155 | joinActiveTestContexts(extensionContext); 156 | } 157 | 158 | @Override 159 | public void interceptDynamicTest(Invocation invocation, DynamicTestInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { 160 | invocation.proceed(); 161 | joinActiveTestContexts(extensionContext); 162 | } 163 | 164 | @Override 165 | public void interceptAfterEachMethod(Invocation invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { 166 | invocation.proceed(); 167 | joinActiveTestContexts(extensionContext); 168 | } 169 | 170 | private void joinActiveTestContexts(ExtensionContext extensionContext) throws Exception { 171 | if (extensionContext.getExecutionException().isPresent()) { 172 | return; 173 | } 174 | 175 | ContextList currentContexts = store(extensionContext).remove(TEST_CONTEXT_KEY, ContextList.class); 176 | if (currentContexts != null) { 177 | for (VertxTestContext context : currentContexts) { 178 | int timeoutDuration = DEFAULT_TIMEOUT_DURATION; 179 | TimeUnit timeoutUnit = DEFAULT_TIMEOUT_UNIT; 180 | Optional testMethod = extensionContext.getTestMethod(); 181 | if (testMethod.isPresent() && testMethod.get().isAnnotationPresent(Timeout.class)) { 182 | Timeout annotation = extensionContext.getRequiredTestMethod().getAnnotation(Timeout.class); 183 | timeoutDuration = annotation.value(); 184 | timeoutUnit = annotation.timeUnit(); 185 | } else { 186 | for ( 187 | Class testClass = extensionContext.getRequiredTestClass(); 188 | testClass != null; 189 | testClass = testClass.isAnnotationPresent(Nested.class) ? testClass.getEnclosingClass() : null 190 | ) { 191 | if (testClass.isAnnotationPresent(Timeout.class)) { 192 | Timeout annotation = testClass.getAnnotation(Timeout.class); 193 | timeoutDuration = annotation.value(); 194 | timeoutUnit = annotation.timeUnit(); 195 | break; 196 | } 197 | } 198 | } 199 | if (context.awaitCompletion(timeoutDuration, timeoutUnit)) { 200 | if (context.failed()) { 201 | Throwable throwable = context.causeOfFailure(); 202 | if (throwable instanceof Exception) { 203 | throw (Exception) throwable; 204 | } else { 205 | throw new AssertionError(throwable); 206 | } 207 | } 208 | } else { 209 | String message = "The test execution timed out. Make sure your asynchronous code " 210 | + "includes calls to either VertxTestContext#completeNow(), VertxTestContext#failNow() " 211 | + "or Checkpoint#flag()"; 212 | message = message + context.unsatisfiedCheckpointCallSites() 213 | .stream() 214 | .map(element -> String.format("-> checkpoint at %s", element)) 215 | .collect(Collectors.joining("\n", "\n\nUnsatisfied checkpoints diagnostics:\n", "")); 216 | throw new TimeoutException(message); 217 | } 218 | } 219 | } 220 | 221 | if (extensionContext.getParent().isPresent()) { 222 | joinActiveTestContexts(extensionContext.getParent().get()); 223 | } 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/junit5/VertxExtensionParameterProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Red Hat, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.vertx.junit5; 18 | 19 | import org.junit.jupiter.api.extension.ExtensionContext; 20 | import org.junit.jupiter.api.extension.ParameterContext; 21 | 22 | /** 23 | * A {@link VertxExtension} test method parameter provider service provider interface. 24 | *

25 | * You can register new providers by pointing to implementations in the 26 | * {@code META-INF/services/io.vertx.junit5.VertxExtensionParameterProvider} resource. 27 | * 28 | * @param Parameter type 29 | * @author Julien Ponge 30 | */ 31 | public interface VertxExtensionParameterProvider { 32 | 33 | /** 34 | * The parameter type. 35 | * 36 | * @return the parameter type 37 | */ 38 | Class type(); 39 | 40 | /** 41 | * A string to identify the parameter in an extension context. 42 | *

43 | * In most cases it should be a constant. 44 | * 45 | * @return the identifier 46 | */ 47 | String key(); 48 | 49 | /** 50 | * Provide a new parameter instance. 51 | * 52 | * @param extensionContext the extension context 53 | * @param parameterContext the parameter context 54 | * @return the new instance 55 | */ 56 | T newInstance(ExtensionContext extensionContext, ParameterContext parameterContext); 57 | 58 | /** 59 | * A consumer to close the resource. 60 | * 61 | * @return the consumer 62 | */ 63 | ParameterClosingConsumer parameterClosingConsumer(); 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/junit5/VertxParameterProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Red Hat, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.vertx.junit5; 18 | 19 | import io.vertx.core.Vertx; 20 | import io.vertx.core.VertxException; 21 | import io.vertx.core.VertxOptions; 22 | import io.vertx.core.buffer.Buffer; 23 | import io.vertx.core.internal.logging.Logger; 24 | import io.vertx.core.internal.logging.LoggerFactory; 25 | import io.vertx.core.json.JsonObject; 26 | import org.junit.jupiter.api.extension.ExtensionContext; 27 | import org.junit.jupiter.api.extension.ParameterContext; 28 | 29 | import java.nio.file.Files; 30 | import java.nio.file.Path; 31 | import java.nio.file.Paths; 32 | import java.util.concurrent.CountDownLatch; 33 | import java.util.concurrent.TimeoutException; 34 | import java.util.concurrent.atomic.AtomicReference; 35 | 36 | import static io.vertx.junit5.VertxExtension.*; 37 | 38 | public class VertxParameterProvider implements VertxExtensionParameterProvider { 39 | 40 | private static final Logger LOG = LoggerFactory.getLogger(VertxParameterProvider.class); 41 | 42 | // Visible for testing 43 | public static final String VERTX_PARAMETER_FILENAME = "vertx.parameter.filename"; 44 | public static final String VERTX_PARAMETER_FILENAME_ENV_VAR = "VERTX_PARAMETER_FILENAME"; 45 | public static final String VERTX_PARAMETER_FILENAME_SYS_PROP = "vertx.parameter.filename"; 46 | 47 | private static final String DEPRECATION_WARNING = String.format( 48 | "'%s' environment variable is deprecated and will be removed in a future version, use '%s' instead", 49 | VERTX_PARAMETER_FILENAME, 50 | VERTX_PARAMETER_FILENAME_ENV_VAR 51 | ); 52 | 53 | @Override 54 | public Class type() { 55 | return Vertx.class; 56 | } 57 | 58 | @Override 59 | public String key() { 60 | return VertxExtension.VERTX_INSTANCE_KEY; 61 | } 62 | 63 | @Override 64 | public Vertx newInstance(ExtensionContext extensionContext, ParameterContext parameterContext) { 65 | 66 | final JsonObject parameters = this.getVertxOptions(); 67 | final VertxOptions options = new VertxOptions(parameters); 68 | return Vertx.vertx(options); 69 | } 70 | 71 | @Override 72 | public ParameterClosingConsumer parameterClosingConsumer() { 73 | return vertx -> { 74 | CountDownLatch latch = new CountDownLatch(1); 75 | AtomicReference errorBox = new AtomicReference<>(); 76 | vertx.close().onComplete(ar -> { 77 | if (ar.failed()) { 78 | errorBox.set(ar.cause()); 79 | } 80 | latch.countDown(); 81 | }); 82 | if (!latch.await(DEFAULT_TIMEOUT_DURATION, DEFAULT_TIMEOUT_UNIT)) { 83 | throw new TimeoutException("Closing the Vertx context timed out"); 84 | } 85 | Throwable throwable = errorBox.get(); 86 | if (throwable != null) { 87 | if (throwable instanceof Exception) { 88 | throw (Exception) throwable; 89 | } else { 90 | throw new VertxException(throwable); 91 | } 92 | } 93 | }; 94 | } 95 | 96 | public JsonObject getVertxOptions() { 97 | String optionFileName = System.getenv(VERTX_PARAMETER_FILENAME_ENV_VAR); 98 | if (optionFileName == null) { 99 | optionFileName = System.getProperty(VERTX_PARAMETER_FILENAME_SYS_PROP); 100 | if (optionFileName == null) { 101 | optionFileName = System.getenv(VERTX_PARAMETER_FILENAME); 102 | if (optionFileName != null) { 103 | LOG.warn(DEPRECATION_WARNING); 104 | } else { 105 | return new JsonObject(); 106 | } 107 | } 108 | } 109 | try { 110 | Path path = Paths.get(optionFileName); 111 | Buffer content = Buffer.buffer(Files.readAllBytes(path)); 112 | return new JsonObject(content); 113 | } catch (Exception e) { 114 | LOG.warn("Failure when reading Vert.x options file, will use default options", e); 115 | return new JsonObject(); 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/junit5/VertxTestContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 Red Hat, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.vertx.junit5; 18 | 19 | import io.vertx.core.*; 20 | 21 | import java.util.HashSet; 22 | import java.util.Objects; 23 | import java.util.Set; 24 | import java.util.concurrent.CountDownLatch; 25 | import java.util.concurrent.TimeUnit; 26 | import java.util.stream.Collectors; 27 | 28 | /** 29 | * A test context to wait on the outcomes of asynchronous operations. 30 | * 31 | * @author Julien Ponge 32 | */ 33 | public final class VertxTestContext { 34 | 35 | /** 36 | * Interface for an executable block of assertion code. 37 | * 38 | * @see #verify(ExecutionBlock) 39 | */ 40 | @FunctionalInterface 41 | public interface ExecutionBlock { 42 | 43 | void apply() throws Throwable; 44 | } 45 | 46 | // ........................................................................................... // 47 | 48 | private Throwable throwableReference = null; 49 | private boolean done = false; 50 | private final CountDownLatch releaseLatch = new CountDownLatch(1); 51 | private final HashSet checkpoints = new HashSet<>(); 52 | 53 | // ........................................................................................... // 54 | 55 | /** 56 | * Check if the context has been marked has failed or not. 57 | * 58 | * @return {@code true} if the context has failed, {@code false} otherwise. 59 | */ 60 | public synchronized boolean failed() { 61 | return throwableReference != null; 62 | } 63 | 64 | /** 65 | * Give the cause of failure. 66 | * 67 | * @return the cause of failure, or {@code null} if the test context hasn't failed. 68 | */ 69 | public synchronized Throwable causeOfFailure() { 70 | return throwableReference; 71 | } 72 | 73 | /** 74 | * Check if the context has completed. 75 | * 76 | * @return {@code true} if the context has completed, {@code false} otherwise. 77 | */ 78 | public synchronized boolean completed() { 79 | return !failed() && (done || releaseLatch.getCount() == 0); 80 | } 81 | 82 | /** 83 | * Gives the call sites of all unsatisfied checkpoints. 84 | * 85 | * @return a set of {@link StackTraceElement} references pointing to the unsatisfied checkpoint call sites. 86 | */ 87 | public Set unsatisfiedCheckpointCallSites() { 88 | return checkpoints 89 | .stream() 90 | .filter(checkpoint -> !checkpoint.satisfied()) 91 | .map(CountingCheckpoint::creationCallSite) 92 | .collect(Collectors.toSet()); 93 | } 94 | 95 | // ........................................................................................... // 96 | 97 | /** 98 | * Complete the test context immediately, making the corresponding test pass. 99 | */ 100 | public synchronized void completeNow() { 101 | done = true; 102 | releaseLatch.countDown(); 103 | } 104 | 105 | /** 106 | * Make the test context fail immediately, making the corresponding test fail. 107 | * 108 | * @param t the cause of failure. 109 | */ 110 | public synchronized void failNow(Throwable t) { 111 | Objects.requireNonNull(t, "The exception cannot be null"); 112 | if (throwableReference == null) { 113 | throwableReference = t; 114 | releaseLatch.countDown(); 115 | } 116 | } 117 | 118 | /** 119 | * Calls {@link #failNow(Throwable)} with the {@code message}. 120 | * 121 | * @param message the cause of failure 122 | */ 123 | public synchronized void failNow(String message) { 124 | failNow(VertxException.noStackTrace(message)); 125 | } 126 | 127 | // ........................................................................................... // 128 | 129 | private synchronized void checkpointSatisfied(Checkpoint checkpoint) { 130 | checkpoints.remove(checkpoint); 131 | if (checkpoints.isEmpty()) { 132 | completeNow(); 133 | } 134 | } 135 | 136 | /** 137 | * Create a lax checkpoint. 138 | * 139 | * @return a checkpoint that requires 1 pass; more passes are allowed and ignored. 140 | */ 141 | public Checkpoint laxCheckpoint() { 142 | return laxCheckpoint(1); 143 | } 144 | 145 | /** 146 | * Create a lax checkpoint. 147 | * 148 | * @param requiredNumberOfPasses the required number of passes to validate the checkpoint. 149 | * @return a checkpoint that requires several passes; more passes than the required number are allowed and ignored. 150 | */ 151 | public synchronized Checkpoint laxCheckpoint(int requiredNumberOfPasses) { 152 | if (done) { 153 | throw new IllegalStateException("Context has already been completed"); 154 | } 155 | CountingCheckpoint checkpoint = CountingCheckpoint.laxCountingCheckpoint(this::checkpointSatisfied, requiredNumberOfPasses); 156 | checkpoints.add(checkpoint); 157 | return checkpoint; 158 | } 159 | 160 | /** 161 | * Create a strict checkpoint. 162 | * 163 | * @return a checkpoint that requires 1 pass, and makes the context fail if it is called more than once. 164 | */ 165 | public Checkpoint checkpoint() { 166 | return checkpoint(1); 167 | } 168 | 169 | /** 170 | * Create a strict checkpoint. 171 | * 172 | * @param requiredNumberOfPasses the required number of passes to validate the checkpoint. 173 | * @return a checkpoint that requires several passes, but no more, or it fails the context. 174 | */ 175 | public synchronized Checkpoint checkpoint(int requiredNumberOfPasses) { 176 | if (done) { 177 | throw new IllegalStateException("Context has already been completed"); 178 | } 179 | CountingCheckpoint checkpoint = CountingCheckpoint.strictCountingCheckpoint(this::checkpointSatisfied, this::failNow, requiredNumberOfPasses); 180 | checkpoints.add(checkpoint); 181 | return checkpoint; 182 | } 183 | 184 | // ........................................................................................... // 185 | 186 | /** 187 | * Create an asynchronous result handler that expects a success, and passes the value to another handler. 188 | * 189 | * @param nextHandler the value handler to call on success that is expected not to throw a {@link Throwable}. 190 | * @param the asynchronous result type. 191 | * @return the handler. 192 | */ 193 | public Handler> succeeding(Handler nextHandler) { 194 | Objects.requireNonNull(nextHandler, "The handler cannot be null"); 195 | return ar -> { 196 | if (ar.failed()) { 197 | failNow(ar.cause()); 198 | return; 199 | } 200 | try { 201 | nextHandler.handle(ar.result()); 202 | } catch (Throwable e) { 203 | failNow(e); 204 | } 205 | }; 206 | } 207 | 208 | /** 209 | * Create an asynchronous result handler that expects a failure. 210 | * 211 | * @param the asynchronous result type. 212 | * @return the handler. 213 | * @deprecated Use {@link #failingThenComplete()} or {@link #failing(Handler)}, for example 214 | * failing(e -> checkpoint.flag()), failing(e -> { more testing code }), or 215 | * failing(e -> {}). 216 | */ 217 | @Deprecated 218 | public Handler> failing() { 219 | return ar -> { 220 | if (ar.succeeded()) { 221 | failNow(new AssertionError("The asynchronous result was expected to have failed")); 222 | } 223 | }; 224 | } 225 | 226 | /** 227 | * Create an asynchronous result handler that expects a failure, and passes the exception to another handler. 228 | * 229 | * @param nextHandler the exception handler to call on failure that is expected not to throw a {@link Throwable}. 230 | * @param the asynchronous result type. 231 | * @return the handler. 232 | */ 233 | public Handler> failing(Handler nextHandler) { 234 | Objects.requireNonNull(nextHandler, "The handler cannot be null"); 235 | return ar -> { 236 | if (ar.succeeded()) { 237 | failNow(new AssertionError("The asynchronous result was expected to have failed")); 238 | return; 239 | } 240 | try { 241 | nextHandler.handle(ar.cause()); 242 | } catch (Throwable e) { 243 | failNow(e); 244 | } 245 | }; 246 | } 247 | 248 | /** 249 | * Create an asynchronous result handler that expects a success to then complete the test context. 250 | * 251 | * @param the asynchronous result type. 252 | * @return the handler. 253 | */ 254 | public Handler> succeedingThenComplete() { 255 | return ar -> { 256 | if (ar.succeeded()) { 257 | completeNow(); 258 | } else { 259 | failNow(ar.cause()); 260 | } 261 | }; 262 | } 263 | 264 | /** 265 | * Create an asynchronous result handler that expects a success to then complete the test context. 266 | * 267 | * @param the asynchronous result type. 268 | * @return the handler. 269 | * @see #failingThenComplete() 270 | * @deprecated Use {@link #succeedingThenComplete()} instead. 271 | */ 272 | @Deprecated 273 | public Handler> completing() { 274 | return succeedingThenComplete(); 275 | } 276 | 277 | /** 278 | * Create an asynchronous result handler that expects a failure to then complete the test context. 279 | * 280 | * @param the asynchronous result type. 281 | * @return the handler. 282 | */ 283 | public Handler> failingThenComplete() { 284 | return ar -> { 285 | if (ar.succeeded()) { 286 | failNow(new AssertionError("The asynchronous result was expected to have failed")); 287 | return; 288 | } 289 | completeNow(); 290 | }; 291 | } 292 | 293 | // ........................................................................................... // 294 | 295 | /** 296 | * This method allows you to check if a future is completed. 297 | * It internally creates a checkpoint. 298 | * You can use it in a chain of `Future`. 299 | * 300 | * @param fut The future to assert success 301 | * @return a future with completion result 302 | */ 303 | public Future assertComplete(Future fut) { 304 | Promise newPromise = Promise.promise(); 305 | fut.onComplete(ar -> { 306 | if (ar.succeeded()) { 307 | newPromise.complete(ar.result()); 308 | } else { 309 | Throwable ex = new AssertionError("Future failed with exception: " + ar.cause().getMessage(), ar.cause()); 310 | this.failNow(ex); 311 | newPromise.fail(ex); 312 | } 313 | }); 314 | return newPromise.future(); 315 | } 316 | 317 | /** 318 | * This method allows you to check if a future is failed. 319 | * It internally creates a checkpoint. 320 | * You can use it in a chain of `Future`. 321 | * 322 | * @param fut The future to assert failure 323 | * @return a future with failure result 324 | */ 325 | public Future assertFailure(Future fut) { 326 | Promise newPromise = Promise.promise(); 327 | fut.onComplete(ar -> { 328 | if (ar.succeeded()) { 329 | Throwable ex = new AssertionError("Future completed with value: " + ar.result()); 330 | this.failNow(ex); 331 | newPromise.fail(ex); 332 | } else { 333 | newPromise.fail(ar.cause()); 334 | } 335 | }); 336 | return newPromise.future(); 337 | } 338 | 339 | // ........................................................................................... // 340 | 341 | /** 342 | * Allow verifications and assertions to be made. 343 | *

344 | * This method allows any assertion API to be used. 345 | * The semantic is that the verification is successful when no exception is being thrown upon calling {@code block}, 346 | * otherwise the context fails with that exception. 347 | * 348 | * @param block a block of code to execute. 349 | * @return this context. 350 | */ 351 | public VertxTestContext verify(ExecutionBlock block) { 352 | Objects.requireNonNull(block, "The block cannot be null"); 353 | try { 354 | block.apply(); 355 | } catch (Throwable t) { 356 | failNow(t); 357 | } 358 | return this; 359 | } 360 | 361 | // ........................................................................................... // 362 | 363 | /** 364 | * Wait for the completion of the test context. 365 | *

366 | * This method is automatically called by the {@link VertxExtension} when using parameter injection of {@link VertxTestContext}. 367 | * You should only call it when you instantiate this class manually. 368 | * 369 | * @param timeout the timeout. 370 | * @param unit the timeout unit. 371 | * @return {@code true} if the completion or failure happens before the timeout has been reached, {@code false} otherwise. 372 | * @throws InterruptedException when the thread has been interrupted. 373 | */ 374 | public boolean awaitCompletion(long timeout, TimeUnit unit) throws InterruptedException { 375 | return failed() || releaseLatch.await(timeout, unit); 376 | } 377 | 378 | // ........................................................................................... // 379 | } 380 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/junit5/VertxTestContextParameterProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Red Hat, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.vertx.junit5; 18 | 19 | import org.junit.jupiter.api.extension.ExtensionContext; 20 | import org.junit.jupiter.api.extension.ParameterContext; 21 | 22 | /** 23 | * Vert.x test context parameter provider holder class. 24 | *

25 | * The implementation does not do anything since that type is a built-in special case, but we need it for the injection 26 | * mechanism to operate. 27 | * 28 | * @author Julien Ponge 29 | */ 30 | public class VertxTestContextParameterProvider implements VertxExtensionParameterProvider { 31 | 32 | @Override 33 | public Class type() { 34 | return VertxTestContext.class; 35 | } 36 | 37 | @Override 38 | public String key() { 39 | doNotCallMe(); 40 | return null; 41 | } 42 | 43 | @Override 44 | public VertxTestContext newInstance(ExtensionContext extensionContext, ParameterContext parameterContext) { 45 | doNotCallMe(); 46 | return null; 47 | } 48 | 49 | @Override 50 | public ParameterClosingConsumer parameterClosingConsumer() { 51 | doNotCallMe(); 52 | return null; 53 | } 54 | 55 | private ParameterClosingConsumer doNotCallMe() { 56 | throw new UnsupportedOperationException("VertxTestContext is a built-in special case"); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/junit5/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 Red Hat, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * This module offers support for writing Vert.x tests with JUnit 5. 19 | * 20 | * @author Julien Ponge 21 | */ 22 | package io.vertx.junit5; 23 | -------------------------------------------------------------------------------- /src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | import org.junit.jupiter.api.extension.ParameterResolver; 2 | 3 | module io.vertx.testing.junit5 { 4 | 5 | requires static io.vertx.docgen; 6 | 7 | requires static org.assertj.core; // Examples 8 | 9 | requires io.vertx.core; 10 | requires io.vertx.core.logging; 11 | requires org.junit.jupiter.params; 12 | 13 | exports io.vertx.junit5; 14 | 15 | uses io.vertx.junit5.VertxExtensionParameterProvider; 16 | 17 | provides ParameterResolver with io.vertx.junit5.VertxExtension; 18 | provides io.vertx.junit5.VertxExtensionParameterProvider with io.vertx.junit5.VertxParameterProvider, io.vertx.junit5.VertxTestContextParameterProvider; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/io.vertx.junit5.VertxExtensionParameterProvider: -------------------------------------------------------------------------------- 1 | io.vertx.junit5.VertxParameterProvider 2 | io.vertx.junit5.VertxTestContextParameterProvider 3 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/junit5/tests/AbstractTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation 3 | * 4 | * This program and the accompanying materials are made available under the 5 | * terms of the Eclipse Public License 2.0 which is available at 6 | * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | * which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | * 9 | * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | */ 11 | 12 | package io.vertx.junit5.tests; 13 | 14 | import io.vertx.core.Vertx; 15 | import io.vertx.junit5.VertxTestContext; 16 | import org.junit.jupiter.api.BeforeAll; 17 | import org.junit.jupiter.api.BeforeEach; 18 | 19 | import java.util.concurrent.atomic.AtomicInteger; 20 | 21 | import static org.junit.jupiter.api.Assertions.assertEquals; 22 | import static org.junit.jupiter.api.Assertions.assertTrue; 23 | 24 | public abstract class AbstractTest { 25 | 26 | public static final AtomicInteger counter = new AtomicInteger(); 27 | 28 | @BeforeAll 29 | static void superBeforeAll(Vertx vertx, VertxTestContext testContext) { 30 | testContext.verify(() -> expectations(vertx, testContext, 0)); 31 | } 32 | 33 | @BeforeEach 34 | void superBeforeEach(Vertx vertx, VertxTestContext testContext) { 35 | testContext.verify(() -> expectations(vertx, testContext, 2)); 36 | } 37 | 38 | protected static void expectations(Vertx vertx, VertxTestContext testContext, int expected) { 39 | assertEquals(expected, counter.get()); 40 | vertx.setTimer(20, l -> { 41 | testContext.verify(() -> { 42 | assertTrue(counter.compareAndSet(expected, expected + 1)); 43 | testContext.completeNow(); 44 | }); 45 | }); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/junit5/tests/AsyncBeforeAllTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation 3 | * 4 | * This program and the accompanying materials are made available under the 5 | * terms of the Eclipse Public License 2.0 which is available at 6 | * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | * which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | * 9 | * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | */ 11 | package io.vertx.junit5.tests; 12 | 13 | import io.vertx.core.Vertx; 14 | import io.vertx.junit5.Checkpoint; 15 | import io.vertx.junit5.VertxExtension; 16 | import io.vertx.junit5.VertxTestContext; 17 | import org.junit.jupiter.api.BeforeAll; 18 | import org.junit.jupiter.api.DisplayName; 19 | import org.junit.jupiter.api.Test; 20 | import org.junit.jupiter.api.extension.ExtendWith; 21 | 22 | import java.util.concurrent.atomic.AtomicBoolean; 23 | import java.util.concurrent.atomic.AtomicInteger; 24 | 25 | import static org.junit.jupiter.api.Assertions.*; 26 | 27 | @ExtendWith(VertxExtension.class) 28 | @DisplayName("Test multiple @BeforeAll methods") 29 | class AsyncBeforeAllTest { 30 | 31 | private static final AtomicBoolean started1 = new AtomicBoolean(); 32 | private static final AtomicBoolean started2 = new AtomicBoolean(); 33 | private static final AtomicInteger count = new AtomicInteger(); 34 | 35 | @BeforeAll 36 | static void before1(VertxTestContext context, Vertx vertx) { 37 | checkBeforeMethod(context, vertx, started1, started2); 38 | } 39 | 40 | @BeforeAll 41 | static void before2(VertxTestContext context, Vertx vertx) { 42 | checkBeforeMethod(context, vertx, started2, started1); 43 | } 44 | 45 | private static void checkBeforeMethod(VertxTestContext context, Vertx vertx, AtomicBoolean mine, AtomicBoolean other) { 46 | int c = count.get(); 47 | if (c == 0) { 48 | assertFalse(mine.get()); 49 | assertFalse(other.get()); 50 | } else if (c == 1) { 51 | assertFalse(mine.get()); 52 | assertTrue(other.get()); 53 | } 54 | Checkpoint checkpoint = context.checkpoint(); 55 | vertx.setTimer(20, id -> { 56 | mine.set(true); 57 | count.incrementAndGet(); 58 | checkpoint.flag(); 59 | }); 60 | } 61 | 62 | @Test 63 | void check_async_before_completed() { 64 | assertEquals(2, count.get()); 65 | assertTrue(started1.get()); 66 | assertTrue(started2.get()); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/junit5/tests/AsyncBeforeCombinedTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 Red Hat, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.vertx.junit5.tests; 17 | 18 | import io.vertx.core.Vertx; 19 | import io.vertx.junit5.Checkpoint; 20 | import io.vertx.junit5.VertxExtension; 21 | import io.vertx.junit5.VertxTestContext; 22 | import org.junit.jupiter.api.BeforeAll; 23 | import org.junit.jupiter.api.BeforeEach; 24 | import org.junit.jupiter.api.DisplayName; 25 | import org.junit.jupiter.api.Test; 26 | import org.junit.jupiter.api.extension.ExtendWith; 27 | 28 | import static org.junit.jupiter.api.Assertions.assertEquals; 29 | 30 | @ExtendWith(VertxExtension.class) 31 | @DisplayName("Test @BeforeEach and @BeforeAll methods") 32 | class AsyncBeforeCombinedTest { 33 | 34 | private static volatile int step; 35 | 36 | @BeforeAll 37 | static void before_all(VertxTestContext context, Vertx vertx) { 38 | assertEquals(0, step); 39 | Checkpoint checkpoint = context.checkpoint(); 40 | vertx.setTimer(200, id -> { 41 | step = 1; 42 | checkpoint.flag(); 43 | }); 44 | } 45 | 46 | @BeforeEach 47 | void before_each(VertxTestContext context, Vertx vertx) { 48 | assertEquals(1, step); 49 | Checkpoint checkpoint = context.checkpoint(); 50 | vertx.setTimer(200, id -> { 51 | step = 2; 52 | checkpoint.flag(); 53 | }); 54 | } 55 | 56 | @Test 57 | void check_async_before_completed() { 58 | assertEquals(2, step); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/junit5/tests/AsyncBeforeEachTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation 3 | * 4 | * This program and the accompanying materials are made available under the 5 | * terms of the Eclipse Public License 2.0 which is available at 6 | * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | * which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | * 9 | * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | */ 11 | package io.vertx.junit5.tests; 12 | 13 | import io.vertx.core.Vertx; 14 | import io.vertx.junit5.Checkpoint; 15 | import io.vertx.junit5.VertxExtension; 16 | import io.vertx.junit5.VertxTestContext; 17 | import org.junit.jupiter.api.BeforeEach; 18 | import org.junit.jupiter.api.DisplayName; 19 | import org.junit.jupiter.api.RepeatedTest; 20 | import org.junit.jupiter.api.extension.ExtendWith; 21 | 22 | import java.util.concurrent.atomic.AtomicBoolean; 23 | import java.util.concurrent.atomic.AtomicInteger; 24 | 25 | import static org.junit.jupiter.api.Assertions.*; 26 | 27 | @ExtendWith(VertxExtension.class) 28 | @DisplayName("Test multiple @BeforeEach methods") 29 | class AsyncBeforeEachTest { 30 | 31 | private final AtomicBoolean started1 = new AtomicBoolean(); 32 | private final AtomicBoolean started2 = new AtomicBoolean(); 33 | private final AtomicInteger count = new AtomicInteger(); 34 | 35 | @BeforeEach 36 | void before1(VertxTestContext context, Vertx vertx) { 37 | checkBeforeMethod(context, vertx, started1, started2); 38 | } 39 | 40 | @BeforeEach 41 | void before2(VertxTestContext context, Vertx vertx) { 42 | checkBeforeMethod(context, vertx, started2, started1); 43 | } 44 | 45 | private void checkBeforeMethod(VertxTestContext context, Vertx vertx, AtomicBoolean mine, AtomicBoolean other) { 46 | int c = count.get(); 47 | if (c == 0) { 48 | assertFalse(mine.get()); 49 | assertFalse(other.get()); 50 | } else if (c == 1) { 51 | assertFalse(mine.get()); 52 | assertTrue(other.get()); 53 | } 54 | Checkpoint checkpoint = context.checkpoint(); 55 | vertx.setTimer(20, id -> { 56 | mine.set(true); 57 | count.incrementAndGet(); 58 | checkpoint.flag(); 59 | }); 60 | } 61 | 62 | @RepeatedTest(10) 63 | void check_async_before_completed() { 64 | assertEquals(2, count.get()); 65 | assertTrue(started1.get()); 66 | assertTrue(started2.get()); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/junit5/tests/ConcreteTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation 3 | * 4 | * This program and the accompanying materials are made available under the 5 | * terms of the Eclipse Public License 2.0 which is available at 6 | * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | * which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | * 9 | * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | */ 11 | 12 | package io.vertx.junit5.tests; 13 | 14 | import io.vertx.core.Vertx; 15 | import io.vertx.junit5.VertxExtension; 16 | import io.vertx.junit5.VertxTestContext; 17 | import org.junit.jupiter.api.BeforeAll; 18 | import org.junit.jupiter.api.BeforeEach; 19 | import org.junit.jupiter.api.Test; 20 | import org.junit.jupiter.api.extension.ExtendWith; 21 | 22 | @ExtendWith(VertxExtension.class) 23 | public class ConcreteTest extends AbstractTest { 24 | 25 | @BeforeAll 26 | static void beforeAll(Vertx vertx, VertxTestContext testContext) { 27 | testContext.verify(() -> expectations(vertx, testContext, 1)); 28 | } 29 | 30 | @BeforeEach 31 | void beforeEach(Vertx vertx, VertxTestContext testContext) { 32 | testContext.verify(() -> expectations(vertx, testContext, 3)); 33 | } 34 | 35 | @Test 36 | void testMethod(Vertx vertx, VertxTestContext testContext) { 37 | testContext.verify(() -> expectations(vertx, testContext, 4)); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/junit5/tests/CountingCheckpointTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 Red Hat, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.vertx.junit5.tests; 18 | 19 | import io.vertx.junit5.Checkpoint; 20 | import io.vertx.junit5.CountingCheckpoint; 21 | import org.junit.jupiter.api.DisplayName; 22 | import org.junit.jupiter.api.Test; 23 | 24 | import java.util.concurrent.atomic.AtomicBoolean; 25 | import java.util.concurrent.atomic.AtomicReference; 26 | import java.util.function.Consumer; 27 | 28 | import static org.assertj.core.api.Assertions.assertThat; 29 | import static org.junit.jupiter.api.Assertions.assertThrows; 30 | 31 | /** 32 | * @author Julien Ponge 33 | */ 34 | @DisplayName("Unit test for CountingCheckpoint") 35 | class CountingCheckpointTest { 36 | 37 | @Test 38 | @DisplayName("Smoke tests") 39 | void smoke_test() { 40 | AtomicBoolean success = new AtomicBoolean(false); 41 | AtomicReference witness = new AtomicReference<>(); 42 | Consumer consumer = c -> { 43 | success.set(true); 44 | witness.set(c); 45 | }; 46 | CountingCheckpoint checkpoint = CountingCheckpoint.laxCountingCheckpoint(consumer, 3); 47 | 48 | checkpoint.flag(); 49 | assertThat(success).isFalse(); 50 | assertThat(witness).hasValue(null); 51 | assertThat(checkpoint.satisfied()).isFalse(); 52 | 53 | checkpoint.flag(); 54 | assertThat(success).isFalse(); 55 | assertThat(witness).hasValue(null); 56 | assertThat(checkpoint.satisfied()).isFalse(); 57 | 58 | checkpoint.flag(); 59 | assertThat(success).isTrue(); 60 | assertThat(witness).hasValue(checkpoint); 61 | assertThat(checkpoint.satisfied()).isTrue(); 62 | } 63 | 64 | private static final Consumer NOOP = c -> { 65 | }; 66 | 67 | @Test 68 | @DisplayName("Refuse null triggers") 69 | void refuse_null_triggers() { 70 | assertThrows(NullPointerException.class, () -> CountingCheckpoint.laxCountingCheckpoint(null, 1)); 71 | assertThrows(NullPointerException.class, () -> CountingCheckpoint.strictCountingCheckpoint(v -> { 72 | }, null, 1)); 73 | } 74 | 75 | @Test 76 | @DisplayName("Refuse having 0 expected passes") 77 | void refuse_zero_passes() { 78 | assertThrows(IllegalArgumentException.class, () -> CountingCheckpoint.laxCountingCheckpoint(NOOP, 0)); 79 | } 80 | 81 | @Test 82 | @DisplayName("Refuse having negative expected passes") 83 | void refuse_negative_passes() { 84 | assertThrows(IllegalArgumentException.class, () -> CountingCheckpoint.laxCountingCheckpoint(NOOP, -1)); 85 | } 86 | 87 | @Test 88 | @DisplayName("Check of a lax checkpoint") 89 | void check_lax_checkpoint() { 90 | CountingCheckpoint checkpoint = CountingCheckpoint.laxCountingCheckpoint(NOOP, 1); 91 | checkpoint.flag(); 92 | checkpoint.flag(); 93 | } 94 | 95 | @Test 96 | @DisplayName("Check of a strict checkpoint") 97 | void check_strict_checkpoint() { 98 | AtomicReference box = new AtomicReference<>(); 99 | CountingCheckpoint checkpoint = CountingCheckpoint.strictCountingCheckpoint(NOOP, box::set, 1); 100 | 101 | assertThat(checkpoint.satisfied()).isFalse(); 102 | checkpoint.flag(); 103 | assertThat(box).hasValue(null); 104 | assertThat(checkpoint.satisfied()).isTrue(); 105 | 106 | checkpoint.flag(); 107 | assertThat(box.get()) 108 | .isNotNull() 109 | .isInstanceOf(IllegalStateException.class) 110 | .hasMessage("Strict checkpoint flagged too many times"); 111 | assertThat(checkpoint.satisfied()).isTrue(); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/junit5/tests/CustomizedRunOnContextExtensionTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Red Hat, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.vertx.junit5.tests; 18 | 19 | import io.vertx.core.Context; 20 | import io.vertx.core.Future; 21 | import io.vertx.core.Vertx; 22 | import io.vertx.junit5.RunTestOnContext; 23 | import org.junit.jupiter.api.AfterAll; 24 | import org.junit.jupiter.api.AfterEach; 25 | import org.junit.jupiter.api.BeforeEach; 26 | import org.junit.jupiter.api.Test; 27 | import org.junit.jupiter.api.extension.RegisterExtension; 28 | 29 | import java.util.concurrent.atomic.AtomicInteger; 30 | 31 | import static org.junit.jupiter.api.Assertions.*; 32 | 33 | public class CustomizedRunOnContextExtensionTest { 34 | 35 | static AtomicInteger destroyMethodInvocations = new AtomicInteger(); 36 | Vertx expectedVertx; 37 | 38 | @RegisterExtension 39 | RunTestOnContext testOnContext = new RunTestOnContext(() -> { 40 | expectedVertx = Vertx.vertx(); 41 | return Future.succeededFuture(expectedVertx); 42 | }, vertx -> { 43 | destroyMethodInvocations.incrementAndGet(); 44 | assertSame(expectedVertx, vertx); 45 | return vertx.close(); 46 | }); 47 | 48 | @BeforeEach 49 | void beforeTest() { 50 | Context ctx = Vertx.currentContext(); 51 | assertNotNull(ctx); 52 | assertSame(expectedVertx, ctx.owner()); 53 | } 54 | 55 | @Test 56 | void testMethod1() { 57 | Context ctx = Vertx.currentContext(); 58 | assertNotNull(ctx); 59 | assertSame(expectedVertx, ctx.owner()); 60 | } 61 | 62 | @Test 63 | void testMethod2() { 64 | Context ctx = Vertx.currentContext(); 65 | assertNotNull(ctx); 66 | assertSame(expectedVertx, ctx.owner()); 67 | } 68 | 69 | @AfterEach 70 | void tearDown() { 71 | Context ctx = Vertx.currentContext(); 72 | assertNotNull(ctx); 73 | assertSame(expectedVertx, ctx.owner()); 74 | } 75 | 76 | @AfterAll 77 | static void afterAll() { 78 | assertEquals(2, destroyMethodInvocations.get()); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/junit5/tests/IntegrationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 Red Hat, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.vertx.junit5.tests; 18 | 19 | import io.vertx.core.*; 20 | import io.vertx.core.http.HttpClient; 21 | import io.vertx.core.http.HttpClientResponse; 22 | import io.vertx.core.http.HttpMethod; 23 | import io.vertx.junit5.Checkpoint; 24 | import io.vertx.junit5.Timeout; 25 | import io.vertx.junit5.VertxExtension; 26 | import io.vertx.junit5.VertxTestContext; 27 | import org.junit.jupiter.api.DisplayName; 28 | import org.junit.jupiter.api.Nested; 29 | import org.junit.jupiter.api.Test; 30 | import org.junit.jupiter.api.extension.ExtendWith; 31 | 32 | import java.util.concurrent.CountDownLatch; 33 | import java.util.concurrent.TimeUnit; 34 | 35 | import static org.assertj.core.api.Assertions.assertThat; 36 | 37 | /** 38 | * @author Julien Ponge 39 | */ 40 | @DisplayName("Integration tests") 41 | class IntegrationTest { 42 | 43 | static class HttpServerVerticle extends VerticleBase { 44 | 45 | @Override 46 | public Future start() throws Exception { 47 | return vertx 48 | .createHttpServer() 49 | .requestHandler(request -> request.response().end("Plop")) 50 | .listen(8080); 51 | } 52 | } 53 | 54 | @Nested 55 | @DisplayName("Tests without parameter injection and explicit Vertx and VertxTestContext instances") 56 | class Naked { 57 | 58 | @Test 59 | @DisplayName("Start a HTTP server") 60 | void start_http_server() throws InterruptedException { 61 | VertxTestContext testContext = new VertxTestContext(); 62 | 63 | Vertx vertx = Vertx.vertx(); 64 | vertx.createHttpServer() 65 | .requestHandler(req -> req.response().end()) 66 | .listen(16969).onComplete(testContext.succeeding(ar -> testContext.completeNow())); 67 | 68 | assertThat(testContext.awaitCompletion(5, TimeUnit.SECONDS)).isTrue(); 69 | closeVertx(vertx); 70 | } 71 | 72 | @Test 73 | @DisplayName("Start a HTTP server, then issue a HTTP client request and check the response") 74 | void vertx_check_http_server_response() throws InterruptedException { 75 | Vertx vertx = Vertx.vertx(); 76 | VertxTestContext testContext = new VertxTestContext(); 77 | 78 | vertx.deployVerticle(new HttpServerVerticle()).onComplete(testContext.succeeding(id -> { 79 | HttpClient client = vertx.createHttpClient(); 80 | client.request(HttpMethod.GET, 8080, "localhost", "/") 81 | .flatMap(req -> req.send().compose(HttpClientResponse::body)) 82 | .onFailure(testContext::failNow) 83 | .onSuccess(buffer -> testContext.verify(() -> { 84 | assertThat(buffer.toString()).isEqualTo("Plop"); 85 | testContext.completeNow(); 86 | })); 87 | })); 88 | 89 | assertThat(testContext.awaitCompletion(5, TimeUnit.SECONDS)).isTrue(); 90 | closeVertx(vertx); 91 | } 92 | 93 | private void closeVertx(Vertx vertx) throws InterruptedException { 94 | CountDownLatch latch = new CountDownLatch(1); 95 | vertx.close().onComplete(ar -> latch.countDown()); 96 | assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue(); 97 | } 98 | } 99 | 100 | @Nested 101 | @ExtendWith(VertxExtension.class) 102 | @DisplayName("Tests with parameter injection") 103 | class WithExtension { 104 | 105 | private HttpClient client; 106 | 107 | @Test 108 | @Timeout(10_000) 109 | @DisplayName("Start a HTTP server, make 10 client requests, and use several checkpoints") 110 | void start_and_request_http_server_with_checkpoints(Vertx vertx, VertxTestContext testContext) { 111 | Checkpoint serverStarted = testContext.checkpoint(); 112 | Checkpoint requestsServed = testContext.checkpoint(10); 113 | Checkpoint responsesReceived = testContext.checkpoint(10); 114 | 115 | vertx.createHttpServer() 116 | .requestHandler(serverRequest -> { 117 | serverRequest.response().end("Ok"); 118 | requestsServed.flag(); 119 | }) 120 | .listen(8080).onComplete(ar -> { 121 | if (ar.failed()) { 122 | testContext.failNow(ar.cause()); 123 | } else { 124 | serverStarted.flag(); 125 | client = vertx.createHttpClient(); 126 | for (int i = 0; i < 10; i++) { 127 | client.request(HttpMethod.GET, 8080, "localhost", "/") 128 | .flatMap(clientRequest -> clientRequest.send().compose(HttpClientResponse::body)) 129 | .onFailure(testContext::failNow) 130 | .onSuccess(buffer -> { 131 | testContext.verify(() -> assertThat(buffer.toString()).isEqualTo("Ok")); 132 | responsesReceived.flag(); 133 | }); 134 | } 135 | } 136 | }); 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/junit5/tests/RunOnContextExtensionTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Red Hat, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.vertx.junit5.tests; 18 | 19 | import io.vertx.core.Context; 20 | import io.vertx.core.Vertx; 21 | import io.vertx.junit5.RunTestOnContext; 22 | import org.junit.jupiter.api.*; 23 | import org.junit.jupiter.api.extension.RegisterExtension; 24 | 25 | import java.util.concurrent.atomic.AtomicReference; 26 | 27 | import static io.vertx.junit5.tests.StaticRunOnContextExtensionTest.checkContext; 28 | import static org.junit.jupiter.api.Assertions.*; 29 | 30 | public class RunOnContextExtensionTest { 31 | 32 | @RegisterExtension 33 | RunTestOnContext testOnContext = new RunTestOnContext(); 34 | 35 | AtomicReference ctxRef = new AtomicReference<>(); 36 | 37 | @BeforeAll 38 | static void beforeAll() { 39 | assertNull(Vertx.currentContext()); 40 | } 41 | 42 | public RunOnContextExtensionTest() { 43 | assertNull(Vertx.currentContext()); 44 | } 45 | 46 | @BeforeEach 47 | void beforeTest() { 48 | Context ctx = Vertx.currentContext(); 49 | assertNotNull(ctx); 50 | assertSame(ctx.owner(), testOnContext.vertx()); 51 | assertNotSame(ctx, ctxRef.getAndSet(ctx)); 52 | } 53 | 54 | @Test 55 | void testMethod1() { 56 | checkContext(testOnContext.vertx(), ctxRef.get()); 57 | } 58 | 59 | @Test 60 | void testMethod2() { 61 | checkContext(testOnContext.vertx(), ctxRef.get()); 62 | } 63 | 64 | @AfterEach 65 | void tearDown() { 66 | checkContext(testOnContext.vertx(), ctxRef.get()); 67 | } 68 | 69 | @AfterAll 70 | static void afterAll() { 71 | assertNull(Vertx.currentContext()); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/junit5/tests/StaticRunOnContextExtensionTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Red Hat, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.vertx.junit5.tests; 18 | 19 | import io.vertx.core.Context; 20 | import io.vertx.core.Vertx; 21 | import io.vertx.junit5.RunTestOnContext; 22 | import io.vertx.junit5.VertxExtension; 23 | import org.junit.jupiter.api.*; 24 | import org.junit.jupiter.api.extension.ExtendWith; 25 | import org.junit.jupiter.api.extension.RegisterExtension; 26 | 27 | import java.util.concurrent.atomic.AtomicReference; 28 | 29 | import static org.junit.jupiter.api.Assertions.*; 30 | 31 | @ExtendWith({VertxExtension.class}) 32 | public class StaticRunOnContextExtensionTest { 33 | 34 | @RegisterExtension 35 | static RunTestOnContext testOnContext = new RunTestOnContext(); 36 | 37 | static AtomicReference ctxRef = new AtomicReference<>(); 38 | 39 | @BeforeAll 40 | static void beforeAll() { 41 | Context ctx = Vertx.currentContext(); 42 | assertNotNull(ctx); 43 | assertSame(ctx.owner(), testOnContext.vertx()); 44 | ctxRef.set(ctx); 45 | } 46 | 47 | public StaticRunOnContextExtensionTest() { 48 | assertNull(Vertx.currentContext()); 49 | } 50 | 51 | @BeforeEach 52 | void beforeTest() { 53 | checkContext(testOnContext.vertx(), ctxRef.get()); 54 | } 55 | 56 | @Test 57 | void testMethod1() { 58 | checkContext(testOnContext.vertx(), ctxRef.get()); 59 | } 60 | 61 | @Test 62 | void testMethod2() { 63 | checkContext(testOnContext.vertx(), ctxRef.get()); 64 | } 65 | 66 | @AfterEach 67 | void tearDown() { 68 | checkContext(testOnContext.vertx(), ctxRef.get()); 69 | } 70 | 71 | @AfterAll 72 | static void afterAll() { 73 | checkContext(testOnContext.vertx(), ctxRef.get()); 74 | } 75 | 76 | static void checkContext(Vertx expectedVertx, Context expectedContext) { 77 | Context ctx = Vertx.currentContext(); 78 | assertNotNull(ctx); 79 | assertSame(expectedVertx, ctx.owner()); 80 | assertSame(expectedContext, ctx); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/junit5/tests/VertxExtensionCompleteLifecycleInjectionTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 Red Hat, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.vertx.junit5.tests; 18 | 19 | import io.vertx.core.Vertx; 20 | import io.vertx.junit5.VertxExtension; 21 | import io.vertx.junit5.VertxTestContext; 22 | import org.junit.jupiter.api.*; 23 | import org.junit.jupiter.api.extension.ExtendWith; 24 | 25 | import static org.assertj.core.api.Assertions.assertThat; 26 | 27 | /** 28 | * @author Julien Ponge 29 | */ 30 | @DisplayName("Test the injection in a complete lifecycle (esp., with @BeforeEach)") 31 | @ExtendWith(VertxExtension.class) 32 | class VertxExtensionCompleteLifecycleInjectionTest { 33 | 34 | static Vertx daVertx; 35 | static VertxTestContext daContext; 36 | 37 | @BeforeAll 38 | static void inTheBeginning(Vertx vertx, VertxTestContext testContext) { 39 | daVertx = vertx; 40 | daContext = testContext; 41 | testContext.completeNow(); 42 | } 43 | 44 | @BeforeEach 45 | void rightBefore(Vertx vertx, VertxTestContext testContext) { 46 | assertThat(vertx).isSameAs(daVertx); 47 | assertThat(testContext).isNotSameAs(daContext); 48 | testContext.completeNow(); 49 | } 50 | 51 | @Test 52 | @DisplayName("Check that the Vertx instance is shared and that VertxTestContext is fresh") 53 | void test1(Vertx vertx, VertxTestContext testContext) { 54 | assertThat(vertx).isSameAs(daVertx); 55 | assertThat(testContext).isNotSameAs(daContext); 56 | testContext.completeNow(); 57 | } 58 | 59 | @Test 60 | @DisplayName("Same test, same assumptions") 61 | void test2(Vertx vertx, VertxTestContext testContext) { 62 | assertThat(vertx).isSameAs(daVertx); 63 | assertThat(testContext).isNotSameAs(daContext); 64 | testContext.completeNow(); 65 | } 66 | 67 | @AfterEach 68 | void rightAfter(Vertx vertx, VertxTestContext testContext) { 69 | assertThat(vertx).isSameAs(daVertx); 70 | assertThat(testContext).isNotSameAs(daContext); 71 | testContext.completeNow(); 72 | } 73 | 74 | @AfterAll 75 | static void inTheEnd(Vertx vertx, VertxTestContext testContext) { 76 | assertThat(vertx).isSameAs(daVertx); 77 | assertThat(testContext).isNotSameAs(daContext); 78 | testContext.completeNow(); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/junit5/tests/VertxExtensionTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 Red Hat, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.vertx.junit5.tests; 18 | 19 | import io.vertx.core.VerticleBase; 20 | import io.vertx.core.Vertx; 21 | import io.vertx.junit5.Checkpoint; 22 | import io.vertx.junit5.Timeout; 23 | import io.vertx.junit5.VertxExtension; 24 | import io.vertx.junit5.VertxTestContext; 25 | import org.junit.jupiter.api.*; 26 | import org.junit.jupiter.api.extension.ExtendWith; 27 | import org.junit.platform.launcher.Launcher; 28 | import org.junit.platform.launcher.LauncherDiscoveryRequest; 29 | import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; 30 | import org.junit.platform.launcher.core.LauncherFactory; 31 | import org.junit.platform.launcher.listeners.SummaryGeneratingListener; 32 | import org.junit.platform.launcher.listeners.TestExecutionSummary; 33 | 34 | import java.util.concurrent.TimeUnit; 35 | import java.util.concurrent.TimeoutException; 36 | 37 | import static org.assertj.core.api.Assertions.assertThat; 38 | import static org.junit.jupiter.api.Assertions.*; 39 | import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; 40 | 41 | /** 42 | * @author Julien Ponge 43 | */ 44 | @DisplayName("Tests of VertxExtension") 45 | class VertxExtensionTest { 46 | 47 | @Nested 48 | @ExtendWith(VertxExtension.class) 49 | @DisplayName("Basic test-level parameter injection smoke tests") 50 | class Injection { 51 | 52 | @Test 53 | @DisplayName("Inject a Vertx instance") 54 | void gimme_vertx(Vertx vertx) { 55 | assertNotNull(vertx); 56 | } 57 | 58 | @Test 59 | @DisplayName("Inject a VertxTestContext instance") 60 | void gimme_vertx_test_context(VertxTestContext context) { 61 | assertNotNull(context); 62 | context.completeNow(); 63 | } 64 | 65 | @Test 66 | @DisplayName("Inject Vertx and VertxTestContext instances") 67 | void gimme_everything(Vertx vertx, VertxTestContext context) { 68 | assertNotNull(vertx); 69 | assertNotNull(context); 70 | context.completeNow(); 71 | } 72 | 73 | @Test 74 | @DisplayName("Inject 2 Vertx instances and check they are the same") 75 | void gimme_2_vertx(Vertx vertx1, Vertx vertx2) { 76 | assertSame(vertx1, vertx2); 77 | } 78 | 79 | @Test 80 | @DisplayName("Inject 2 VertxTestContext instances and check they are different") 81 | void gimme_2_vertx(VertxTestContext context1, VertxTestContext context2) { 82 | assertNotSame(context1, context2); 83 | context1.completeNow(); 84 | context2.completeNow(); 85 | } 86 | } 87 | 88 | @Nested 89 | @ExtendWith(VertxExtension.class) 90 | @io.vertx.junit5.Timeout(4500) 91 | @DisplayName("Specify timeouts") 92 | class SpecifyTimeout { 93 | 94 | @Test 95 | @DisplayName("Override a class-level timeout") 96 | @io.vertx.junit5.Timeout(value = 5, timeUnit = TimeUnit.SECONDS) 97 | void a(VertxTestContext context) throws InterruptedException { 98 | Thread.sleep(50); 99 | context.completeNow(); 100 | } 101 | 102 | @Test 103 | @DisplayName("Use the class-level timeout") 104 | void b(VertxTestContext context) throws InterruptedException { 105 | Thread.sleep(50); 106 | context.completeNow(); 107 | } 108 | } 109 | 110 | @Nested 111 | @DisplayName("Tests that require embedding a JUnit launcher") 112 | class EmbeddedWithARunner { 113 | 114 | @Test 115 | @DisplayName("⚙️ Check a test failure") 116 | void checkFailureTest() { 117 | LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request() 118 | .selectors(selectClass(FailureTest.class)) 119 | .build(); 120 | Launcher launcher = LauncherFactory.create(); 121 | SummaryGeneratingListener listener = new SummaryGeneratingListener(); 122 | launcher.registerTestExecutionListeners(listener); 123 | launcher.execute(request); 124 | TestExecutionSummary summary = listener.getSummary(); 125 | assertThat(summary.getTestsStartedCount()).isEqualTo(1); 126 | assertThat(summary.getTestsFailedCount()).isEqualTo(1); 127 | assertThat(summary.getFailures().get(0).getException()).isInstanceOf(AssertionError.class); 128 | } 129 | 130 | @Nested 131 | @ExtendWith(VertxExtension.class) 132 | @DisplayName("🚫") 133 | class FailureTest { 134 | 135 | @Test 136 | @Tag("programmatic") 137 | void thisMustFail(Vertx vertx, VertxTestContext testContext) { 138 | testContext.verify(() -> { 139 | assertTrue(false); 140 | }); 141 | } 142 | } 143 | 144 | @Test 145 | @DisplayName("⚙️ Check a failure in the test method body rather than in a callback") 146 | void checkDirectFailure() { 147 | LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request() 148 | .selectors(selectClass(DirectFailureTest.class)) 149 | .build(); 150 | Launcher launcher = LauncherFactory.create(); 151 | SummaryGeneratingListener listener = new SummaryGeneratingListener(); 152 | launcher.registerTestExecutionListeners(listener); 153 | launcher.execute(request); 154 | TestExecutionSummary summary = listener.getSummary(); 155 | assertThat(summary.getTestsStartedCount()).isEqualTo(1); 156 | assertThat(summary.getTestsFailedCount()).isEqualTo(1); 157 | assertThat(summary.getFailures().get(0).getException()).isInstanceOf(RuntimeException.class); 158 | } 159 | 160 | @Nested 161 | @ExtendWith(VertxExtension.class) 162 | @DisplayName("🚫") 163 | class DirectFailureTest { 164 | 165 | @Test 166 | @Tag("programmatic") 167 | @io.vertx.junit5.Timeout(value = 1, timeUnit = TimeUnit.SECONDS) 168 | void thisMustFail(VertxTestContext testContext) { 169 | throw new RuntimeException("YOLO"); 170 | } 171 | } 172 | 173 | @Test 174 | @DisplayName("⚙️ Check a test failure with an intermediate async result verifier") 175 | void checkFailureTestWithIntermediateAsyncVerifier() { 176 | LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request() 177 | .selectors(selectClass(FailureWithIntermediateAsyncVerifierTest.class)) 178 | .build(); 179 | Launcher launcher = LauncherFactory.create(); 180 | SummaryGeneratingListener listener = new SummaryGeneratingListener(); 181 | launcher.registerTestExecutionListeners(listener); 182 | launcher.execute(request); 183 | TestExecutionSummary summary = listener.getSummary(); 184 | assertThat(summary.getTestsStartedCount()).isEqualTo(1); 185 | assertThat(summary.getTestsFailedCount()).isEqualTo(1); 186 | assertThat(summary.getFailures().get(0).getException()).isInstanceOf(AssertionError.class); 187 | } 188 | 189 | @Nested 190 | @ExtendWith(VertxExtension.class) 191 | @DisplayName("🚫") 192 | class FailureWithIntermediateAsyncVerifierTest { 193 | 194 | @Test 195 | @Tag("programmatic") 196 | void thisMustAlsoFail(Vertx vertx, VertxTestContext testContext) { 197 | vertx.executeBlocking(() -> { 198 | try { 199 | Thread.sleep(500); 200 | } catch (InterruptedException e) { 201 | e.printStackTrace(); 202 | } 203 | return 69; 204 | }).onComplete(testContext.succeeding(i -> testContext.verify(() -> assertEquals(58, i)))); 205 | } 206 | } 207 | 208 | @Nested 209 | @ExtendWith(VertxExtension.class) 210 | @DisplayName("🚫") 211 | class TimingOut { 212 | 213 | @Test 214 | @Tag("programmatic") 215 | @Timeout(value = 2, timeUnit = TimeUnit.SECONDS) 216 | void doNothing(VertxTestContext testContext) { 217 | testContext.checkpoint(); 218 | } 219 | } 220 | 221 | @Nested 222 | @ExtendWith(VertxExtension.class) 223 | @DisplayName("🚫") 224 | class TooMuchFlagging { 225 | 226 | @Test 227 | @Tag("programmatic") 228 | void flagTooMuch(VertxTestContext testContext) { 229 | Checkpoint checkpoint = testContext.checkpoint(3); 230 | for (int i = 0; i < 10; i++) { 231 | checkpoint.flag(); 232 | } 233 | } 234 | } 235 | 236 | @Test 237 | @DisplayName("⚙️ Check that too much flagging fails tests") 238 | void checkTooMuchFlaggingFails() { 239 | LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request() 240 | .selectors(selectClass(EmbeddedWithARunner.TooMuchFlagging.class)) 241 | .build(); 242 | Launcher launcher = LauncherFactory.create(); 243 | SummaryGeneratingListener listener = new SummaryGeneratingListener(); 244 | launcher.registerTestExecutionListeners(listener); 245 | launcher.execute(request); 246 | TestExecutionSummary summary = listener.getSummary(); 247 | assertThat(summary.getTestsStartedCount()).isEqualTo(1); 248 | assertThat(summary.getTestsFailedCount()).isEqualTo(1); 249 | Throwable exception = summary.getFailures().get(0).getException(); 250 | assertThat(exception) 251 | .isInstanceOf(IllegalStateException.class) 252 | .hasMessage("Strict checkpoint flagged too many times"); 253 | } 254 | 255 | @Test 256 | @DisplayName("⚙️ Check a timeout diagnosis") 257 | void checkTimeoutFailureTestWithIntermediateAsyncVerifier() { 258 | LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request() 259 | .selectors(selectClass(EmbeddedWithARunner.TimingOut.class)) 260 | .build(); 261 | Launcher launcher = LauncherFactory.create(); 262 | SummaryGeneratingListener listener = new SummaryGeneratingListener(); 263 | launcher.registerTestExecutionListeners(listener); 264 | launcher.execute(request); 265 | TestExecutionSummary summary = listener.getSummary(); 266 | assertThat(summary.getTestsStartedCount()).isEqualTo(1); 267 | assertThat(summary.getTestsFailedCount()).isEqualTo(1); 268 | Throwable exception = summary.getFailures().get(0).getException(); 269 | assertThat(exception) 270 | .isInstanceOf(TimeoutException.class) 271 | .hasMessageContaining("checkpoint at io.vertx.testing.junit5.tests/io.vertx.junit5.tests.VertxExtensionTest$EmbeddedWithARunner$TimingOut"); 272 | } 273 | } 274 | 275 | private static class UselessVerticle extends VerticleBase { 276 | } 277 | 278 | @Nested 279 | @ExtendWith(VertxExtension.class) 280 | @DisplayName("Test parameter injection at various (non-static) levels") 281 | class VertxInjectionTest { 282 | 283 | Vertx currentVertx; 284 | VertxTestContext previousTestContext; 285 | 286 | @BeforeEach 287 | void prepare(Vertx vertx, VertxTestContext testContext) { 288 | assertThat(testContext).isNotSameAs(previousTestContext); 289 | previousTestContext = testContext; 290 | assertThat(currentVertx).isNotSameAs(vertx); 291 | currentVertx = vertx; 292 | vertx.deployVerticle(new UselessVerticle()).onComplete(testContext.succeeding(id -> testContext.completeNow())); 293 | } 294 | 295 | @AfterEach 296 | void cleanup(Vertx vertx, VertxTestContext testContext) { 297 | assertThat(testContext).isNotSameAs(previousTestContext); 298 | previousTestContext = testContext; 299 | assertThat(vertx.deploymentIDs()).isNotEmpty().hasSize(1); 300 | vertx.close().onComplete(testContext.succeeding(v -> testContext.completeNow())); 301 | } 302 | 303 | @RepeatedTest(10) 304 | @DisplayName("Test the validity of references and scoping") 305 | void checkDeployments(Vertx vertx, VertxTestContext testContext) { 306 | assertThat(testContext).isNotSameAs(previousTestContext); 307 | previousTestContext = testContext; 308 | assertThat(vertx).isSameAs(currentVertx); 309 | assertThat(vertx.deploymentIDs()).isNotEmpty().hasSize(1); 310 | testContext.completeNow(); 311 | } 312 | 313 | @Nested 314 | @DisplayName("A nested test") 315 | class NestedTest { 316 | 317 | @RepeatedTest(10) 318 | @DisplayName("Test the validity of references and scoping") 319 | void checkDeployments(Vertx vertx, VertxTestContext testContext) { 320 | assertThat(testContext).isNotSameAs(previousTestContext); 321 | previousTestContext = testContext; 322 | assertThat(vertx).isSameAs(currentVertx); 323 | assertThat(vertx.deploymentIDs()).isNotEmpty().hasSize(1); 324 | testContext.completeNow(); 325 | } 326 | } 327 | } 328 | } 329 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/junit5/tests/VertxParameterProviderTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Red Hat, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.vertx.junit5.tests; 18 | 19 | import io.vertx.core.json.JsonObject; 20 | import io.vertx.junit5.VertxParameterProvider; 21 | import org.junit.jupiter.api.DisplayName; 22 | import org.junit.jupiter.api.Test; 23 | import org.junit.jupiter.api.condition.DisabledForJreRange; 24 | import org.junit.jupiter.api.condition.EnabledOnJre; 25 | import org.junit.jupiter.api.condition.JRE; 26 | import org.junit.jupiter.api.io.TempDir; 27 | 28 | import java.nio.file.Files; 29 | import java.nio.file.Path; 30 | 31 | import static com.github.stefanbirkner.systemlambda.SystemLambda.*; 32 | import static io.vertx.junit5.VertxParameterProvider.*; 33 | import static org.junit.jupiter.api.Assertions.*; 34 | 35 | /** 36 | * @author Stephan Wisssel 37 | */ 38 | @DisplayName("Test of VertxParameterProvider") 39 | @EnabledOnJre(value = JRE.JAVA_11) 40 | public class VertxParameterProviderTest { 41 | 42 | VertxParameterProvider provider = new VertxParameterProvider(); 43 | JsonObject expected = new JsonObject(); 44 | JsonObject actual = new JsonObject(); 45 | 46 | @Test 47 | @DisplayName("Default case - empty VertxOptions") 48 | void default_empty_options() { 49 | actual.mergeIn(provider.getVertxOptions()); 50 | assertEquals(expected.encode(), actual.encode(), "Options should be equally empty but are not"); 51 | } 52 | 53 | @Test 54 | @DisplayName("Failed retrieval of options - env var") 55 | void failed_retrieval_of_options_env_var() throws Exception { 56 | failure(true, false); 57 | } 58 | 59 | @Test 60 | @DisplayName("Failed retrieval of options - old env var") 61 | void failed_retrieval_of_options_old_env_var() throws Exception { 62 | failure(true, true); 63 | } 64 | 65 | @Test 66 | @DisplayName("Failed retrieval of options - sys prop") 67 | void failed_retrieval_of_options_sys_prop() throws Exception { 68 | failure(false, false); 69 | } 70 | 71 | private void failure(boolean useEnv, boolean oldEnv) throws Exception { 72 | String doesNotExist = "something.that.does.not.exist.json"; 73 | if (useEnv) { 74 | String var = oldEnv ? VERTX_PARAMETER_FILENAME : VERTX_PARAMETER_FILENAME_ENV_VAR; 75 | withEnvironmentVariable(var, doesNotExist).execute(() -> { 76 | actual.mergeIn(provider.getVertxOptions()); 77 | }); 78 | } else { 79 | restoreSystemProperties(() -> { 80 | System.setProperty(VertxParameterProvider.VERTX_PARAMETER_FILENAME_SYS_PROP, doesNotExist); 81 | actual.mergeIn(provider.getVertxOptions()); 82 | }); 83 | } 84 | assertEquals(expected.encode(), actual.encode(), "Options retrieval failure not handled"); 85 | } 86 | 87 | @Test 88 | @DisplayName("Retrieval of options - env var") 89 | void retrieval_of_options_env_var(@TempDir Path tempDir) throws Exception { 90 | success(tempDir, true, false); 91 | } 92 | 93 | @Test 94 | @DisplayName("Retrieval of options - old env var") 95 | void retrieval_of_options_old_env_var(@TempDir Path tempDir) throws Exception { 96 | success(tempDir, true, true); 97 | } 98 | 99 | @Test 100 | @DisplayName("Retrieval of options - sys prop") 101 | void retrieval_of_options_sys_prop(@TempDir Path tempDir) throws Exception { 102 | success(tempDir, false, false); 103 | } 104 | 105 | private void success(Path tempDir, boolean useEnv, boolean oldEnv) throws Exception { 106 | expected.mergeIn(new JsonObject() 107 | .put("BlockedThreadCheckInterval", 120) 108 | .put("MaxWorkerExecuteTime", 42)); 109 | 110 | // Create a temp file and populate it with our expected values 111 | Path tempOptionFile = tempDir.resolve("VertxOptions.json").toAbsolutePath(); 112 | Files.write(tempOptionFile, expected.toBuffer().getBytes()); 113 | 114 | if (useEnv) { 115 | String var = oldEnv ? VERTX_PARAMETER_FILENAME : VERTX_PARAMETER_FILENAME_ENV_VAR; 116 | withEnvironmentVariable(var, tempOptionFile.toString()).execute(() -> { 117 | actual.mergeIn(provider.getVertxOptions()); 118 | }); 119 | } else { 120 | restoreSystemProperties(() -> { 121 | System.setProperty(VERTX_PARAMETER_FILENAME_SYS_PROP, tempOptionFile.toString()); 122 | actual.mergeIn(provider.getVertxOptions()); 123 | }); 124 | } 125 | 126 | assertEquals(expected.encode(), actual.encode(), "Options retrieval failed"); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/junit5/tests/VertxTestContextTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 Red Hat, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.vertx.junit5.tests; 18 | 19 | import io.vertx.core.Future; 20 | import io.vertx.core.Handler; 21 | import io.vertx.junit5.Checkpoint; 22 | import io.vertx.junit5.VertxTestContext; 23 | import org.junit.jupiter.api.DisplayName; 24 | import org.junit.jupiter.api.Test; 25 | 26 | import java.util.concurrent.TimeUnit; 27 | import java.util.concurrent.atomic.AtomicBoolean; 28 | 29 | import static org.assertj.core.api.Assertions.assertThat; 30 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 31 | 32 | /** 33 | * @author Julien Ponge 34 | */ 35 | @DisplayName("Unit tests for VertxTestContext") 36 | class VertxTestContextTest { 37 | 38 | @Test 39 | @DisplayName("Check that failing with a null exception is forbidden") 40 | void fail_with_null() { 41 | VertxTestContext context = new VertxTestContext(); 42 | assertThatThrownBy(() -> context.failNow((Throwable) null)) 43 | .isInstanceOf(NullPointerException.class) 44 | .hasMessage("The exception cannot be null"); 45 | } 46 | 47 | @Test 48 | @DisplayName("Check the behavior of failing()") 49 | void check_async_assert_fail() throws InterruptedException { 50 | VertxTestContext context = new VertxTestContext(); 51 | context.failing().handle(Future.failedFuture("Bam")); 52 | context.awaitCompletion(1, TimeUnit.MILLISECONDS); 53 | assertThat(context.failed()).isFalse(); 54 | 55 | context = new VertxTestContext(); 56 | context.failing().handle(Future.succeededFuture()); 57 | context.awaitCompletion(1, TimeUnit.MILLISECONDS); 58 | assertThat(context.failed()).isTrue(); 59 | assertThat(context.causeOfFailure()).hasMessage("The asynchronous result was expected to have failed"); 60 | } 61 | 62 | @Test 63 | @DisplayName("Check the behavior of succeeding(callback)") 64 | void check_async_assert_with_handler() throws InterruptedException { 65 | AtomicBoolean checker = new AtomicBoolean(false); 66 | VertxTestContext context = new VertxTestContext(); 67 | 68 | VertxTestContext finalContext = context; 69 | Handler nextHandler = obj -> { 70 | checker.set(true); 71 | finalContext.completeNow(); 72 | }; 73 | 74 | context.succeeding(nextHandler).handle(Future.succeededFuture()); 75 | assertThat(context.awaitCompletion(2, TimeUnit.SECONDS)).isTrue(); 76 | assertThat(context.completed()).isTrue(); 77 | assertThat(checker).isTrue(); 78 | 79 | checker.set(false); 80 | context = new VertxTestContext(); 81 | 82 | context.succeeding(nextHandler).handle(Future.failedFuture(new RuntimeException("Plop"))); 83 | assertThat(context.awaitCompletion(2, TimeUnit.SECONDS)).isTrue(); 84 | assertThat(context.failed()).isTrue(); 85 | assertThat(context.causeOfFailure()) 86 | .isInstanceOf(RuntimeException.class) 87 | .hasMessage("Plop"); 88 | assertThat(checker).isFalse(); 89 | } 90 | 91 | @Test 92 | @DisplayName("Check callback exception of succeeding(callback)") 93 | void check_succeeding_callback_with_exception() throws InterruptedException { 94 | VertxTestContext context = new VertxTestContext(); 95 | Handler nextHandler = obj -> { 96 | throw new RuntimeException("Boom"); 97 | }; 98 | context.succeeding(nextHandler).handle(Future.succeededFuture()); 99 | assertThat(context.awaitCompletion(2, TimeUnit.SECONDS)).isTrue(); 100 | assertThat(context.failed()).isTrue(); 101 | assertThat(context.causeOfFailure()) 102 | .isInstanceOf(RuntimeException.class) 103 | .hasMessage("Boom"); 104 | } 105 | 106 | @Test 107 | @DisplayName("Check the behavior of failing(callback)") 108 | void check_async_assert_fail_with_handler() throws InterruptedException { 109 | AtomicBoolean checker = new AtomicBoolean(false); 110 | VertxTestContext context = new VertxTestContext(); 111 | 112 | VertxTestContext finalContext = context; 113 | Handler nextHandler = ar -> { 114 | checker.set(true); 115 | finalContext.completeNow(); 116 | }; 117 | 118 | context.failing(nextHandler).handle(Future.failedFuture("Bam")); 119 | assertThat(context.awaitCompletion(2, TimeUnit.SECONDS)).isTrue(); 120 | assertThat(context.completed()).isTrue(); 121 | assertThat(checker).isTrue(); 122 | 123 | checker.set(false); 124 | context = new VertxTestContext(); 125 | 126 | context.failing(nextHandler).handle(Future.succeededFuture()); 127 | assertThat(context.awaitCompletion(2, TimeUnit.SECONDS)).isTrue(); 128 | assertThat(context.failed()).isTrue(); 129 | assertThat(context.causeOfFailure()).hasMessage("The asynchronous result was expected to have failed"); 130 | } 131 | 132 | @Test 133 | @DisplayName("Check callback exception of failing(callback)") 134 | void check_failing_callback_with_exception() throws InterruptedException { 135 | VertxTestContext context = new VertxTestContext(); 136 | Handler nextHandler = throwable -> { 137 | throw new RuntimeException("Pow"); 138 | }; 139 | context.failing(nextHandler).handle(Future.failedFuture("some failure")); 140 | assertThat(context.awaitCompletion(2, TimeUnit.SECONDS)).isTrue(); 141 | assertThat(context.failed()).isTrue(); 142 | assertThat(context.causeOfFailure()) 143 | .isInstanceOf(RuntimeException.class) 144 | .hasMessage("Pow"); 145 | } 146 | 147 | @Test 148 | @DisplayName("Check the behavior of verify() and no error") 149 | void check_verify_ok() throws InterruptedException { 150 | VertxTestContext context = new VertxTestContext(); 151 | context.verify(() -> { 152 | assertThat("ok").isEqualTo("ok"); 153 | context.completeNow(); 154 | }); 155 | assertThat(context.awaitCompletion(500, TimeUnit.MILLISECONDS)).isTrue(); 156 | } 157 | 158 | @Test 159 | @DisplayName("Check the behavior of verify() with an error") 160 | void check_verify_fail() throws InterruptedException { 161 | VertxTestContext context = new VertxTestContext(); 162 | context.verify(() -> { 163 | throw new RuntimeException("Bam"); 164 | }); 165 | assertThat(context.awaitCompletion(500, TimeUnit.MILLISECONDS)).isTrue(); 166 | 167 | assertThat(context.failed()).isTrue(); 168 | assertThat(context.causeOfFailure()) 169 | .isInstanceOf(RuntimeException.class) 170 | .hasMessage("Bam"); 171 | } 172 | 173 | @Test 174 | @DisplayName("Check that flagging 2 checkpoints completes the test context") 175 | void check_checkpoint() throws InterruptedException { 176 | VertxTestContext context = new VertxTestContext(); 177 | 178 | Checkpoint a = context.checkpoint(); 179 | Checkpoint b = context.checkpoint(); 180 | 181 | new Thread(a::flag).start(); 182 | new Thread(b::flag).start(); 183 | assertThat(context.awaitCompletion(500, TimeUnit.MILLISECONDS)).isTrue(); 184 | } 185 | 186 | @Test 187 | @DisplayName("Check that not flagging all checkpoints ends up in a timeout") 188 | void checK_not_all_checkpoints_passed_timesout() throws InterruptedException { 189 | VertxTestContext context = new VertxTestContext(); 190 | 191 | Checkpoint a = context.checkpoint(2); 192 | context.checkpoint(); 193 | 194 | new Thread(a::flag).start(); 195 | new Thread(a::flag).start(); 196 | assertThat(context.awaitCompletion(500, TimeUnit.MILLISECONDS)).isFalse(); 197 | } 198 | 199 | @Test 200 | @DisplayName("Check that flagging strict checkpoints more than expected fails the test context") 201 | void check_strict_checkpoint_overuse() throws InterruptedException { 202 | VertxTestContext context = new VertxTestContext(); 203 | 204 | Checkpoint a = context.checkpoint(); 205 | Checkpoint b = context.checkpoint(); 206 | new Thread(a::flag).start(); 207 | new Thread(a::flag).start(); 208 | 209 | assertThat(context.awaitCompletion(500, TimeUnit.MILLISECONDS)).isTrue(); 210 | assertThat(context.failed()).isTrue(); 211 | assertThat(context.causeOfFailure()) 212 | .isInstanceOf(IllegalStateException.class) 213 | .hasMessageContaining("flagged too many times"); 214 | } 215 | 216 | @Test 217 | @DisplayName("Check that a lax checkpoint can be flagged more often than required") 218 | void check_lax_checkpoint_no_overuse() throws InterruptedException { 219 | VertxTestContext context = new VertxTestContext(); 220 | 221 | Checkpoint a = context.laxCheckpoint(); 222 | Checkpoint b = context.checkpoint(); 223 | new Thread(() -> { 224 | a.flag(); 225 | a.flag(); 226 | a.flag(); 227 | b.flag(); 228 | }).start(); 229 | 230 | assertThat(context.awaitCompletion(500, TimeUnit.MILLISECONDS)).isTrue(); 231 | assertThat(context.failed()).isFalse(); 232 | assertThat(context.completed()).isTrue(); 233 | } 234 | 235 | @Test 236 | @DisplayName("Check that failing an already completed context is possible") 237 | void complete_then_fail() { 238 | VertxTestContext context = new VertxTestContext(); 239 | 240 | context.completeNow(); 241 | context.failNow(new IllegalStateException("Oh")); 242 | 243 | assertThat(context.completed()).isFalse(); 244 | assertThat(context.failed()).isTrue(); 245 | assertThat(context.causeOfFailure()).isInstanceOf(IllegalStateException.class).hasMessage("Oh"); 246 | } 247 | 248 | @Test 249 | @DisplayName("Just fail immediately and on the test runner thread") 250 | void just_fail() throws InterruptedException { 251 | VertxTestContext context = new VertxTestContext(); 252 | context.failNow(new RuntimeException("Woops")); 253 | assertThat(context.awaitCompletion(1, TimeUnit.SECONDS)).isTrue(); 254 | assertThat(context.failed()).isTrue(); 255 | } 256 | 257 | @Test 258 | @DisplayName("Pass a success to a succeedingThenComplete() async handler") 259 | void check_succeedingThenComplete_success() throws InterruptedException { 260 | VertxTestContext context = new VertxTestContext(); 261 | context.succeedingThenComplete().handle(Future.succeededFuture()); 262 | assertThat(context.awaitCompletion(1, TimeUnit.SECONDS)).isTrue(); 263 | assertThat(context.completed()).isTrue(); 264 | } 265 | 266 | @Test 267 | @DisplayName("Pass a failure to a succeedingThenComplete() async handler") 268 | void check_succeedingThenComplete_failure() throws InterruptedException { 269 | VertxTestContext context = new VertxTestContext(); 270 | context.succeedingThenComplete().handle(Future.failedFuture(new RuntimeException("Boo!"))); 271 | assertThat(context.awaitCompletion(1, TimeUnit.SECONDS)).isTrue(); 272 | assertThat(context.completed()).isFalse(); 273 | assertThat(context.failed()).isTrue(); 274 | assertThat(context.causeOfFailure()).hasMessage("Boo!"); 275 | } 276 | 277 | @Test 278 | @DisplayName("Pass a failure to a failingThenComplete() async handler") 279 | void check_failingThenComplete_failure() throws InterruptedException { 280 | VertxTestContext context = new VertxTestContext(); 281 | context.failingThenComplete().handle(Future.failedFuture(new IllegalArgumentException("42"))); 282 | assertThat(context.awaitCompletion(1, TimeUnit.SECONDS)).isTrue(); 283 | assertThat(context.failed()).isFalse(); 284 | assertThat(context.completed()).isTrue(); 285 | assertThat(context.causeOfFailure()).isNull(); 286 | } 287 | 288 | @Test 289 | @DisplayName("Pass a success to a failingThenComplete() async handler") 290 | void check_failingThenComplete_success() throws InterruptedException { 291 | VertxTestContext context = new VertxTestContext(); 292 | context.failingThenComplete().handle(Future.succeededFuture("gold")); 293 | assertThat(context.awaitCompletion(1, TimeUnit.SECONDS)).isTrue(); 294 | assertThat(context.completed()).isFalse(); 295 | assertThat(context.failed()).isTrue(); 296 | assertThat(context.causeOfFailure()) 297 | .isInstanceOf(AssertionError.class) 298 | .hasMessage("The asynchronous result was expected to have failed"); 299 | } 300 | 301 | @Test 302 | @DisplayName("Pass future assertComplete") 303 | void check_future_completion() throws InterruptedException { 304 | VertxTestContext context = new VertxTestContext(); 305 | context 306 | .assertComplete(Future.succeededFuture("bla")) 307 | .compose(s -> context.assertComplete(Future.succeededFuture(s + "bla"))) 308 | .onComplete(context.succeeding(res -> { 309 | assertThat(res).isEqualTo("blabla"); 310 | context.completeNow(); 311 | })); 312 | assertThat(context.awaitCompletion(1, TimeUnit.SECONDS)).isTrue(); 313 | assertThat(context.completed()).isTrue(); 314 | } 315 | 316 | @Test 317 | @DisplayName("Fail future assertComplete") 318 | void check_future_completion_failure() throws InterruptedException { 319 | VertxTestContext context = new VertxTestContext(); 320 | context 321 | .assertComplete(Future.succeededFuture("bla")) 322 | .compose(s -> context.assertComplete(Future.failedFuture(new IllegalStateException(s + "bla")))) 323 | .onComplete(context.succeeding(res -> { 324 | context.completeNow(); 325 | })); 326 | assertThat(context.awaitCompletion(1, TimeUnit.SECONDS)).isTrue(); 327 | assertThat(context.completed()).isFalse(); 328 | assertThat(context.failed()).isTrue(); 329 | assertThat(context.causeOfFailure()) 330 | .isInstanceOf(AssertionError.class); 331 | assertThat(context.causeOfFailure().getCause()) 332 | .isInstanceOf(IllegalStateException.class) 333 | .hasMessage("blabla"); 334 | } 335 | 336 | @Test 337 | @DisplayName("Pass future chain assertComplete") 338 | void check_future_chain_completion() throws InterruptedException { 339 | VertxTestContext context = new VertxTestContext(); 340 | context 341 | .assertComplete(Future.succeededFuture("bla") 342 | .compose(s -> Future.failedFuture(new IllegalStateException(s + "bla"))) 343 | .recover(ex -> Future.succeededFuture(ex.getMessage())) 344 | ) 345 | .onComplete(context.succeeding(res -> { 346 | assertThat(res).isEqualTo("blabla"); 347 | context.completeNow(); 348 | })); 349 | assertThat(context.awaitCompletion(1, TimeUnit.SECONDS)).isTrue(); 350 | assertThat(context.completed()).isTrue(); 351 | } 352 | 353 | @Test 354 | @DisplayName("Fail future chain assertComplete") 355 | void check_future_chain_completion_failure() throws InterruptedException { 356 | VertxTestContext context = new VertxTestContext(); 357 | context 358 | .assertComplete(Future.succeededFuture("bla") 359 | .compose(s -> Future.failedFuture(new IllegalStateException(s + "bla"))) 360 | ) 361 | .onComplete(context.succeeding(res -> { 362 | context.completeNow(); 363 | })); 364 | assertThat(context.awaitCompletion(1, TimeUnit.SECONDS)).isTrue(); 365 | assertThat(context.completed()).isFalse(); 366 | assertThat(context.failed()).isTrue(); 367 | assertThat(context.causeOfFailure()) 368 | .isInstanceOf(AssertionError.class); 369 | assertThat(context.causeOfFailure().getCause()) 370 | .isInstanceOf(IllegalStateException.class) 371 | .hasMessage("blabla"); 372 | } 373 | 374 | @Test 375 | @DisplayName("Pass future assertFailure") 376 | void check_future_failing() throws InterruptedException { 377 | VertxTestContext context = new VertxTestContext(); 378 | context 379 | .assertFailure(Future.failedFuture(new IllegalStateException("bla"))) 380 | .recover(s -> context.assertFailure(Future.failedFuture(new IllegalStateException(s.getMessage() + "bla")))) 381 | .onComplete(context.failing(ex -> { 382 | assertThat(ex) 383 | .isInstanceOf(IllegalStateException.class) 384 | .hasMessage("blabla"); 385 | context.completeNow(); 386 | })); 387 | assertThat(context.awaitCompletion(1, TimeUnit.SECONDS)).isTrue(); 388 | assertThat(context.completed()).isTrue(); 389 | } 390 | 391 | @Test 392 | @DisplayName("Fail future assertComplete") 393 | void check_future_failing_failure() throws InterruptedException { 394 | VertxTestContext context = new VertxTestContext(); 395 | context 396 | .assertFailure(Future.failedFuture(new IllegalStateException("bla"))) 397 | .recover(s -> context.assertFailure(Future.succeededFuture(s.getMessage() + "bla"))) 398 | .onComplete(context.succeeding(res -> { 399 | context.completeNow(); 400 | })); 401 | assertThat(context.awaitCompletion(1, TimeUnit.SECONDS)).isTrue(); 402 | assertThat(context.completed()).isFalse(); 403 | assertThat(context.failed()).isTrue(); 404 | assertThat(context.causeOfFailure()) 405 | .isInstanceOf(AssertionError.class) 406 | .hasMessage("Future completed with value: blabla"); 407 | } 408 | 409 | @Test 410 | @DisplayName("Call verify() with a block that throws an exception") 411 | void check_verify_with_exception() throws InterruptedException { 412 | VertxTestContext context = new VertxTestContext(); 413 | context.verify(() -> { 414 | throw new RuntimeException("!"); 415 | }); 416 | assertThat(context.awaitCompletion(1, TimeUnit.SECONDS)).isTrue(); 417 | assertThat(context.causeOfFailure()) 418 | .hasMessage("!") 419 | .isInstanceOf(RuntimeException.class); 420 | } 421 | 422 | @Test 423 | @DisplayName("Check that unsatisfied call sites are properly identified") 424 | void check_unsatisifed_checkpoint_callsites() { 425 | VertxTestContext context = new VertxTestContext(); 426 | Checkpoint a = context.checkpoint(); 427 | Checkpoint b = context.checkpoint(2); 428 | 429 | assertThat(context.unsatisfiedCheckpointCallSites()).hasSize(2); 430 | 431 | a.flag(); 432 | b.flag(); 433 | assertThat(context.unsatisfiedCheckpointCallSites()).hasSize(1); 434 | 435 | StackTraceElement element = context.unsatisfiedCheckpointCallSites().iterator().next(); 436 | assertThat(element.getClassName()).isEqualTo(VertxTestContextTest.class.getName()); 437 | assertThat(element.getMethodName()).isEqualTo("check_unsatisifed_checkpoint_callsites"); 438 | 439 | b.flag(); 440 | assertThat(context.unsatisfiedCheckpointCallSites()).isEmpty(); 441 | } 442 | 443 | @Test 444 | @DisplayName("Check failNow() called with a string") 445 | void check_fail_now_called_with_a_string() { 446 | VertxTestContext context = new VertxTestContext(); 447 | 448 | context.failNow("error message"); 449 | 450 | assertThat(context.failed()).isTrue(); 451 | assertThat(context.causeOfFailure()).hasMessage("error message"); 452 | } 453 | } 454 | -------------------------------------------------------------------------------- /src/test/java/module-info.java: -------------------------------------------------------------------------------- 1 | open module io.vertx.testing.junit5.tests { 2 | requires io.vertx.core; 3 | requires io.vertx.testing.junit5; 4 | requires org.junit.platform.launcher; 5 | requires org.assertj.core; 6 | requires org.junit.jupiter.api; 7 | requires system.lambda; 8 | } 9 | --------------------------------------------------------------------------------