├── .github ├── CODEOWNERS ├── dependabot.yml ├── project.yml └── workflows │ ├── build.yml │ ├── pre-release.yml │ ├── quarkus-snapshot.yaml │ ├── release-perform.yml │ └── release-prepare.yml ├── .gitignore ├── LICENSE ├── README.md ├── docs ├── antora.yml ├── modules │ └── ROOT │ │ ├── nav.adoc │ │ └── pages │ │ ├── includes │ │ └── attributes.adoc │ │ ├── index.adoc │ │ ├── injectmock.adoc │ │ └── injectspy.adoc └── pom.xml ├── integration-tests ├── pom.xml └── src │ ├── main │ └── kotlin │ │ └── org │ │ └── acme │ │ ├── MessageService.kt │ │ ├── SingletonService.kt │ │ ├── SuffixService.kt │ │ └── resource │ │ └── RestRessource.kt │ └── test │ └── kotlin │ └── org │ └── acme │ ├── NestedTest.kt │ └── SingletonServiceTest.kt ├── junit5-mockk ├── pom.xml └── src │ ├── main │ ├── kotlin │ │ └── io │ │ │ └── quarkiverse │ │ │ └── test │ │ │ └── junit │ │ │ └── mockk │ │ │ ├── InjectMock.kt │ │ │ ├── InjectSpy.kt │ │ │ └── internal │ │ │ ├── CreateMockkMocksCallback.kt │ │ │ ├── CreateMockkSpiesCallback.kt │ │ │ ├── MocksTracker.kt │ │ │ ├── ReflectionUtils.kt │ │ │ ├── ResetMockkMocksCallback.kt │ │ │ ├── ResetOuterMockkMockCallback.kt │ │ │ ├── SetMockkMockAsBeanMockCallback.kt │ │ │ ├── SingletonToApplicationScopedTestBuildChainCustomizerProducer.kt │ │ │ └── UnremoveableMockTestBuildChainCustomizerProducer.kt │ └── resources │ │ └── META-INF │ │ └── services │ │ ├── io.quarkus.test.junit.buildchain.TestBuildChainCustomizerProducer │ │ ├── io.quarkus.test.junit.callback.QuarkusTestAfterAllCallback │ │ ├── io.quarkus.test.junit.callback.QuarkusTestAfterConstructCallback │ │ ├── io.quarkus.test.junit.callback.QuarkusTestAfterEachCallback │ │ └── io.quarkus.test.junit.callback.QuarkusTestBeforeEachCallback │ └── test │ ├── kotlin │ └── io │ │ └── quarkiverse │ │ └── test │ │ └── junit │ │ └── mockk │ │ └── internal │ │ ├── ReflectionUtilsTest.kt │ │ ├── application │ │ └── SimpleSingletonBean.kt │ │ └── example │ │ ├── InjectRelaxedMockTest.kt │ │ ├── InjectRestClientTest.kt │ │ ├── InjectionMockTest.kt │ │ ├── InjectionSpyTest.kt │ │ └── ReturnMutinyTypeTest.kt │ └── resources │ └── application.properties └── pom.xml /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Lines starting with '#' are comments. 2 | # Each line is a file pattern followed by one or more owners. 3 | 4 | # More details are here: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners 5 | 6 | # The '*' pattern is global owners. 7 | 8 | # Order is important. The last matching pattern has the most precedence. 9 | # The folders are ordered as follows: 10 | 11 | # In each subsection folders are ordered first by depth, then alphabetically. 12 | # This should make it easy to add new rules without breaking existing ones. 13 | 14 | * @quarkiverse/quarkiverse-mockk 15 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "maven" 9 | directory: "/" 10 | schedule: 11 | interval: "daily" 12 | ignore: 13 | - dependency-name: "org.apache.maven.plugins:maven-compiler-plugin" 14 | - package-ecosystem: github-actions 15 | directory: "/" 16 | schedule: 17 | interval: "daily" 18 | -------------------------------------------------------------------------------- /.github/project.yml: -------------------------------------------------------------------------------- 1 | name: Quarkiverse Mockk Extension 2 | release: 3 | current-version: 3.0.0 4 | next-version: 999-SNAPSHOT 5 | 6 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - "main" 7 | paths-ignore: 8 | - '.gitignore' 9 | - 'CODEOWNERS' 10 | - 'LICENSE' 11 | - '*.md' 12 | - '*.adoc' 13 | - '*.txt' 14 | - '.all-contributorsrc' 15 | pull_request: 16 | paths-ignore: 17 | - '.gitignore' 18 | - 'CODEOWNERS' 19 | - 'LICENSE' 20 | - '*.md' 21 | - '*.adoc' 22 | - '*.txt' 23 | - '.all-contributorsrc' 24 | 25 | concurrency: 26 | group: ${{ github.workflow }}-${{ github.ref }} 27 | cancel-in-progress: true 28 | 29 | defaults: 30 | run: 31 | shell: bash 32 | 33 | jobs: 34 | build: 35 | name: Build on ${{ matrix.os }} 36 | strategy: 37 | fail-fast: false 38 | matrix: 39 | # os: [windows-latest, macos-latest, ubuntu-latest] 40 | os: [ubuntu-latest] 41 | runs-on: ${{ matrix.os }} 42 | steps: 43 | - name: Prepare git 44 | run: git config --global core.autocrlf false 45 | if: startsWith(matrix.os, 'windows') 46 | 47 | - uses: actions/checkout@v4 48 | - name: Set up JDK 17 49 | uses: actions/setup-java@v4 50 | with: 51 | distribution: temurin 52 | java-version: 17 53 | cache: 'maven' 54 | 55 | - name: Build with Maven 56 | run: mvn -B clean install -Dno-format 57 | 58 | - name: Build with Maven (Native) 59 | run: mvn -B install -Dnative -Dquarkus.native.container-build -Dnative.surefire.skip 60 | -------------------------------------------------------------------------------- /.github/workflows/pre-release.yml: -------------------------------------------------------------------------------- 1 | name: Quarkiverse Pre Release 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - '.github/project.yml' 7 | 8 | concurrency: 9 | group: ${{ github.workflow }}-${{ github.ref }} 10 | cancel-in-progress: true 11 | 12 | jobs: 13 | pre-release: 14 | name: Pre-Release 15 | uses: quarkiverse/.github/.github/workflows/pre-release.yml@main 16 | secrets: inherit 17 | -------------------------------------------------------------------------------- /.github/workflows/quarkus-snapshot.yaml: -------------------------------------------------------------------------------- 1 | name: "Quarkus ecosystem CI" 2 | on: 3 | workflow_dispatch: 4 | watch: 5 | types: [started] 6 | 7 | # For this CI to work, ECOSYSTEM_CI_TOKEN needs to contain a GitHub with rights to close the Quarkus issue that the user/bot has opened, 8 | # while 'ECOSYSTEM_CI_REPO_PATH' needs to be set to the corresponding path in the 'quarkusio/quarkus-ecosystem-ci' repository 9 | 10 | env: 11 | ECOSYSTEM_CI_REPO: quarkusio/quarkus-ecosystem-ci 12 | ECOSYSTEM_CI_REPO_FILE: context.yaml 13 | JAVA_VERSION: 17 14 | 15 | ######################### 16 | # Repo specific setting # 17 | ######################### 18 | 19 | ECOSYSTEM_CI_REPO_PATH: quarkiverse-mockk 20 | 21 | jobs: 22 | build: 23 | name: "Build against latest Quarkus snapshot" 24 | runs-on: ubuntu-latest 25 | # Allow to manually launch the ecosystem CI in addition to the bots 26 | if: github.actor == 'quarkusbot' || github.actor == 'quarkiversebot' || github.actor == 'glefloch' || github.actor == 'janpk' 27 | 28 | steps: 29 | - name: Set up Java 30 | uses: actions/setup-java@v4 31 | with: 32 | distribution: temurin 33 | java-version: ${{ env.JAVA_VERSION }} 34 | 35 | - name: Checkout repo 36 | uses: actions/checkout@v4 37 | with: 38 | path: current-repo 39 | 40 | - name: Checkout Ecosystem 41 | uses: actions/checkout@v4 42 | with: 43 | repository: ${{ env.ECOSYSTEM_CI_REPO }} 44 | path: ecosystem-ci 45 | 46 | - name: Setup and Run Tests 47 | run: ./ecosystem-ci/setup-and-test 48 | env: 49 | ECOSYSTEM_CI_TOKEN: ${{ secrets.ECOSYSTEM_CI_TOKEN }} 50 | -------------------------------------------------------------------------------- /.github/workflows/release-perform.yml: -------------------------------------------------------------------------------- 1 | name: Quarkiverse Perform Release 2 | run-name: Perform ${{github.event.inputs.tag || github.ref_name}} Release 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | workflow_dispatch: 8 | inputs: 9 | tag: 10 | description: 'Tag to release' 11 | required: true 12 | 13 | permissions: 14 | attestations: write 15 | id-token: write 16 | contents: read 17 | 18 | concurrency: 19 | group: ${{ github.workflow }}-${{ github.ref }} 20 | cancel-in-progress: true 21 | 22 | jobs: 23 | perform-release: 24 | name: Perform Release 25 | uses: quarkiverse/.github/.github/workflows/perform-release.yml@main 26 | secrets: inherit 27 | with: 28 | version: ${{github.event.inputs.tag || github.ref_name}} 29 | -------------------------------------------------------------------------------- /.github/workflows/release-prepare.yml: -------------------------------------------------------------------------------- 1 | name: Quarkiverse Prepare Release 2 | 3 | on: 4 | pull_request: 5 | types: [ closed ] 6 | paths: 7 | - '.github/project.yml' 8 | 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.ref }} 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | prepare-release: 15 | name: Prepare Release 16 | if: ${{ github.event.pull_request.merged == true}} 17 | uses: quarkiverse/.github/.github/workflows/prepare-release.yml@main 18 | secrets: inherit 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | 25 | # Eclipse 26 | .project 27 | .classpath 28 | .settings/ 29 | bin/ 30 | 31 | # IntelliJ 32 | .idea 33 | *.ipr 34 | *.iml 35 | *.iws 36 | 37 | # NetBeans 38 | nb-configuration.xml 39 | 40 | # Visual Studio Code 41 | .vscode 42 | .factorypath 43 | 44 | # OSX 45 | .DS_Store 46 | 47 | # Vim 48 | *.swp 49 | *.swo 50 | 51 | # patch 52 | *.orig 53 | *.rej 54 | 55 | # Gradle 56 | .gradle/ 57 | build/ 58 | 59 | # Maven 60 | target/ 61 | pom.xml.tag 62 | pom.xml.releaseBackup 63 | pom.xml.versionsBackup 64 | release.properties 65 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Quarkus JUnit5 MockK Extension 2 | 3 | ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/quarkiverse/quarkus-mockk/build.yml?style=for-the-badge&logo=github) 4 | [![Version](https://img.shields.io/maven-central/v/io.quarkiverse.mockk/quarkus-junit5-mockk?logo=apache-maven&style=for-the-badge)](https://search.maven.org/artifact/io.quarkiverse.mockk/quarkus-junit5-mockk) 5 | [![License](https://img.shields.io/github/license/quarkusio/quarkus?style=for-the-badge&logo=apache)](https://www.apache.org/licenses/LICENSE-2.0) 6 | 7 | ## Description 8 | 9 | This Quarkus JUnit5 MockK extension allows you to easily inject MockK mocks. 10 | 11 | The full documentation be found [here](https://quarkiverse.github.io/quarkiverse-docs/quarkus-mockk/dev/index.html). 12 | 13 | ## Importing the dependency 14 | 15 | First of all, you need to add the following dependency: 16 | 17 | ```xml 18 | 19 | io.quarkiverse.mockk 20 | quarkus-junit5-mockk 21 | LATEST 22 | test 23 | 24 | ``` 25 | If you are using gradle: 26 | 27 | ````groovy 28 | dependencies { 29 | testImplementation 'io.quarkiverse.mockk:quarkus-junit5-mockk:LATEST' 30 | } 31 | ```` 32 | 33 | ## Compatibility with Quarkus 34 | 35 | Starting with version `3.8+`, version `3+` of `quarkus-junit5-mockk` should be used. 36 | If you use a version between `3.0.0`, and before `3.8.0`, version `2.0.0` of `quarkus-junit5-mockk` should be used. 37 | If you use a version between `2.8` and before `3.0.0`, version `1.1.0` of `quarkus-junit5-mockk` should be used. 38 | If you use a version before `2.7`, version `1.0.1` of `quarkus-junit5-mockk` should be used. 39 | 40 | ## Example 41 | 42 | Now, you can use `@InjectMock` and `@InjectSpy` in your test such as: 43 | 44 | ````kotlin 45 | @QuarkusTest 46 | class InjectionMockTest { 47 | 48 | @Inject 49 | private lateinit var firstService: FirstService 50 | 51 | @InjectMock 52 | private lateinit var secondService: SecondService 53 | 54 | @Test 55 | fun `should respond test`() { 56 | every { secondService.greet() } returns "test" 57 | assertThat(firstService.greet()).isEqualTo("test") 58 | } 59 | 60 | @Test 61 | fun `should respond second`() { 62 | every { secondService.greet() } returns "second" 63 | assertThat(firstService.greet()).isEqualTo("second") 64 | verify { secondService.greet() } 65 | } 66 | } 67 | ```` 68 | -------------------------------------------------------------------------------- /docs/antora.yml: -------------------------------------------------------------------------------- 1 | name: quarkus-mockk 2 | title: Mockk 3 | version: dev 4 | nav: 5 | - modules/ROOT/nav.adoc 6 | -------------------------------------------------------------------------------- /docs/modules/ROOT/nav.adoc: -------------------------------------------------------------------------------- 1 | * xref:index.adoc[Introduction] 2 | * xref:injectmock.adoc[Inject Mocks] 3 | * xref:injectspy.adoc[Inject Spies] -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/includes/attributes.adoc: -------------------------------------------------------------------------------- 1 | :quarkus-version: 3.8.4 2 | :mockk-jvm: 1.13.11 3 | :maven-version: 3.8.1+ 4 | 5 | :quarkus-org-url: https://github.com/quarkusio 6 | :quarkus-base-url: {quarkus-org-url}/quarkus 7 | :quarkus-clone-url: {quarkus-base-url}.git 8 | :quarkus-archive-url: {quarkus-base-url}/archive/master.zip 9 | :quarkus-tree-url: {quarkus-base-url}/tree/main 10 | :quarkus-issues-url: {quarkus-base-url}/issues 11 | :quarkus-guides-url: https://quarkus.io/guides 12 | :quickstarts-base-url: https://github.com/quarkusio/quarkus-quickstarts 13 | :quickstarts-clone-url: https://github.com/quarkusio/quarkus-quickstarts.git 14 | :quickstarts-archive-url: https://github.com/quarkusio/quarkus-quickstarts/archive/main.zip 15 | :quickstarts-blob-url: https://github.com/quarkusio/quarkus-quickstarts/blob/main 16 | :quickstarts-tree-url: https://github.com/quarkusio/quarkus-quickstarts/tree/main 17 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/index.adoc: -------------------------------------------------------------------------------- 1 | = Quarkus Mockk Extension 2 | :extension-status: preview 3 | include::./includes/attributes.adoc[] 4 | 5 | This extension allows you to inject MockK mocks in your `QuarkusTest` tests. 6 | 7 | == Installation 8 | 9 | If you want to use this extension, you need to add the `quarkus-junit5-mockk` extension first. 10 | In your `pom.xml` file, add: 11 | 12 | [source,xml] 13 | ---- 14 | 15 | io.quarkiverse.mockk 16 | quarkus-junit5-mockk 17 | LATEST 18 | test 19 | 20 | ---- 21 | 22 | Or add this in your `build.gradle` file: 23 | 24 | [source,groovy] 25 | ---- 26 | dependencies { 27 | testImplementation 'io.quarkiverse.mockk:quarkus-junit5-mockk:LATEST' 28 | } 29 | ---- 30 | 31 | The extension exposes two annotations: 32 | 33 | * `@InjectMock` to inject MockK mocks in your application 34 | * `@InjectSpy` to inject SpyK spies in your application 35 | 36 | == Compatibility with Quarkus 37 | 38 | Starting with version `3.8+`, version `3+` of `quarkus-junit5-mockk` should be used. 39 | If you use a version between ˋ3.0.0`, and before `3.8.0`, version `2.0.0` of `quarkus-junit5-mockk` should be used. 40 | If you use a version between `2.8` and before `3.0.0`, version `1.1.0` of `quarkus-junit5-mockk` should be used. 41 | If you use a version before `2.7`, version `1.0.1` of `quarkus-junit5-mockk` should be used. 42 | 43 | == Known limitations 44 | 45 | For the moment, this library does not : 46 | 47 | * Support mock injection in `@Nested` test classes 48 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/injectmock.adoc: -------------------------------------------------------------------------------- 1 | = `@InjectMock` annotation 2 | 3 | This annotation allows you to inject MockK mocks in your application. 4 | 5 | This annotation has three parameters: 6 | 7 | * `relaxed`, if set to `true`, all function will return simple values. Default to `false`. 8 | * `relaxUnitFun`, if set to `true`, `Unit` function will be relaxed. Default to `false`. 9 | * `convertScopes`, it set to `true`, convert the `@Singleton` scope of bean to `@ApplicationScoped`. This allows to mock singleton beans 10 | 11 | == Example 12 | 13 | For example, `@InjectMock` can be used in the following example: 14 | 15 | [source,kotlin] 16 | ---- 17 | @QuarkusTest 18 | class InjectionMockTest { 19 | 20 | @Inject 21 | private lateinit var firstService: FirstService 22 | 23 | @InjectMock 24 | private lateinit var secondService: SecondService 25 | 26 | @Test 27 | fun `should respond test`() { 28 | every { secondService.greet() } returns "test" 29 | assertThat(firstService.greet()).isEqualTo("test") 30 | } 31 | 32 | @Test 33 | fun `should respond second`() { 34 | every { secondService.greet() } returns "second" 35 | assertThat(firstService.greet()).isEqualTo("second") 36 | verify { secondService.greet() } 37 | } 38 | } 39 | ---- 40 | 41 | In order to mock a `RestClient`, you must add the `@RestClient` qualifier alongside the `@InjectMock`: 42 | 43 | [source,kotlin] 44 | ---- 45 | @QuarkusTest 46 | class InjectionMockTest { 47 | 48 | @Inject 49 | private lateinit var firstService: FirstService 50 | 51 | @InjectMock 52 | @RestClient 53 | private lateinit var myRestClientService: MyRestClientService 54 | 55 | @Test 56 | fun `should respond test`() { 57 | every { myRestClientService.greet() } returns "test" 58 | assertThat(firstService.greet()).isEqualTo("test") 59 | } 60 | } 61 | ---- 62 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/injectspy.adoc: -------------------------------------------------------------------------------- 1 | = `@InjectSpy` annotation 2 | 3 | This annotation allows you to inject spies in your application. 4 | 5 | Spy allows you to mixed real objects and mocks. 6 | 7 | == Example 8 | 9 | For example, `@InjectSpy` can be used in the following example: 10 | 11 | [source,kotlin] 12 | ---- 13 | @QuarkusTest 14 | class InjectionMockTest { 15 | 16 | @InjectSpy 17 | private lateinit var firstService: FirstService 18 | 19 | @Test 20 | fun `should respond test`() { 21 | assertThat(firstService.greet()).isEqualTo("Hello") 22 | } 23 | 24 | @Test 25 | fun `should respond second`() { 26 | every { firstService.greet() } returns "mock response" 27 | assertThat(firstService.greet()).isEqualTo("mock response") 28 | verify { firstService.greet() } 29 | } 30 | } 31 | ---- -------------------------------------------------------------------------------- /docs/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | io.quarkiverse.mockk 6 | quarkus-junit5-mockk-parent 7 | 999-SNAPSHOT 8 | ../pom.xml 9 | 10 | 11 | quarkus-junit5-mockk-docs 12 | Quarkus - JUnit 5 - Mockk - Documentation 13 | 14 | 15 | 16 | 17 | it.ozimov 18 | yaml-properties-maven-plugin 19 | 20 | 21 | initialize 22 | 23 | read-project-properties 24 | 25 | 26 | 27 | ${project.basedir}/../.github/project.yml 28 | 29 | 30 | 31 | 32 | 33 | 34 | maven-resources-plugin 35 | 36 | 37 | copy-resources 38 | generate-resources 39 | 40 | copy-resources 41 | 42 | 43 | ${project.basedir}/modules/ROOT/pages/includes/ 44 | 45 | 46 | ${project.basedir}/../target/asciidoc/generated/config/ 47 | config.adoc 48 | false 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | org.asciidoctor 57 | asciidoctor-maven-plugin 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /integration-tests/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | io.quarkiverse.mockk 7 | quarkus-junit5-mockk-parent 8 | 999-SNAPSHOT 9 | ../pom.xml 10 | 11 | 12 | io.quarkiverse.mockk 13 | quarkus-junit5-mockk-integration-tests 14 | Quarkus - JUnit 5 - Mockk - Integration tests 15 | 16 | 17 | true 18 | 19 | 20 | 21 | 22 | org.jetbrains.kotlin 23 | kotlin-stdlib 24 | ${kotlin.version} 25 | 26 | 27 | io.quarkus 28 | quarkus-resteasy-jackson 29 | 30 | 31 | io.quarkus 32 | quarkus-kotlin 33 | 34 | 35 | io.quarkus 36 | quarkus-junit5 37 | test 38 | 39 | 40 | io.quarkiverse.mockk 41 | quarkus-junit5-mockk 42 | ${project.version} 43 | test 44 | 45 | 46 | org.assertj 47 | assertj-core 48 | ${assertj.version} 49 | test 50 | 51 | 52 | io.rest-assured 53 | rest-assured 54 | test 55 | 56 | 57 | 58 | 59 | src/main/kotlin 60 | src/test/kotlin 61 | 62 | 63 | maven-surefire-plugin 64 | 65 | 66 | org.jboss.logmanager.LogManager 67 | 68 | 69 | 70 | 71 | org.jetbrains.kotlin 72 | kotlin-maven-plugin 73 | ${kotlin.version} 74 | 75 | 76 | compile 77 | compile 78 | 79 | compile 80 | 81 | 82 | 83 | test-compile 84 | test-compile 85 | 86 | test-compile 87 | 88 | 89 | 90 | 91 | 92 | io.quarkus 93 | quarkus-maven-plugin 94 | ${quarkus.version} 95 | 96 | 97 | 98 | build 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /integration-tests/src/main/kotlin/org/acme/MessageService.kt: -------------------------------------------------------------------------------- 1 | package org.acme 2 | 3 | import jakarta.enterprise.context.ApplicationScoped 4 | 5 | @ApplicationScoped 6 | class MessageService { 7 | 8 | fun getMessage() = "message" 9 | 10 | } -------------------------------------------------------------------------------- /integration-tests/src/main/kotlin/org/acme/SingletonService.kt: -------------------------------------------------------------------------------- 1 | package org.acme 2 | 3 | import jakarta.inject.Singleton 4 | 5 | @Singleton 6 | open class SingletonService { 7 | fun greet() = "Hello" 8 | } -------------------------------------------------------------------------------- /integration-tests/src/main/kotlin/org/acme/SuffixService.kt: -------------------------------------------------------------------------------- 1 | package org.acme 2 | 3 | import jakarta.enterprise.context.ApplicationScoped 4 | 5 | @ApplicationScoped 6 | class SuffixService { 7 | 8 | fun getSuffix() = "suffix" 9 | 10 | } -------------------------------------------------------------------------------- /integration-tests/src/main/kotlin/org/acme/resource/RestRessource.kt: -------------------------------------------------------------------------------- 1 | package org.acme.resource 2 | 3 | import org.acme.MessageService 4 | import org.acme.SuffixService 5 | import jakarta.ws.rs.GET 6 | import jakarta.ws.rs.Path 7 | 8 | @Path("/message") 9 | class RestRessource(val messageService: MessageService, val suffixService: SuffixService) { 10 | 11 | @GET 12 | fun message() = "${messageService.getMessage()} ${suffixService.getSuffix()}" 13 | 14 | } -------------------------------------------------------------------------------- /integration-tests/src/test/kotlin/org/acme/NestedTest.kt: -------------------------------------------------------------------------------- 1 | package org.acme 2 | 3 | import io.restassured.RestAssured.given 4 | import org.hamcrest.Matchers.`is` 5 | 6 | import io.mockk.every 7 | import io.quarkiverse.test.junit.mockk.InjectMock 8 | import io.quarkus.test.junit.QuarkusTest 9 | import org.junit.jupiter.api.Nested 10 | import org.junit.jupiter.api.Test 11 | 12 | @QuarkusTest 13 | class NestedTest { 14 | 15 | @InjectMock 16 | lateinit var messageService: MessageService 17 | 18 | @Nested 19 | inner class ActualTest { 20 | 21 | @InjectMock 22 | lateinit var suffixService: SuffixService 23 | 24 | @Test 25 | fun greet() { 26 | every { suffixService.getSuffix() } returns "!!" 27 | every { messageService.getMessage() } returns "mocked" 28 | 29 | given() 30 | .get("/message") 31 | .then() 32 | .statusCode(200) 33 | .body(`is`("mocked !!")) 34 | } 35 | 36 | @Test 37 | fun greetAgain() { 38 | every { suffixService.getSuffix() } returns "!!" 39 | every { messageService.getMessage() } returns "new mocked" 40 | 41 | given() 42 | .get("/message") 43 | .then() 44 | .statusCode(200) 45 | .body(`is`("new mocked !!")) 46 | } 47 | 48 | } 49 | } -------------------------------------------------------------------------------- /integration-tests/src/test/kotlin/org/acme/SingletonServiceTest.kt: -------------------------------------------------------------------------------- 1 | package org.acme 2 | 3 | import io.mockk.every 4 | import io.mockk.verify 5 | import io.quarkiverse.test.junit.mockk.InjectMock 6 | 7 | import io.quarkus.test.junit.QuarkusTest 8 | import org.assertj.core.api.Assertions 9 | import org.junit.jupiter.api.Test 10 | 11 | @QuarkusTest 12 | class MockSingletonBeanTest { 13 | 14 | @InjectMock(convertScopes = true) 15 | lateinit var bean: SingletonService 16 | 17 | @Test 18 | fun `Should be able to use a mocked DataService`() { 19 | every { bean.greet() } returns "mocked" 20 | Assertions.assertThat(bean.greet()).isEqualTo("mocked") 21 | verify { bean.greet() } 22 | } 23 | } -------------------------------------------------------------------------------- /junit5-mockk/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | io.quarkiverse.mockk 7 | quarkus-junit5-mockk-parent 8 | 999-SNAPSHOT 9 | ../pom.xml 10 | 11 | 12 | io.quarkiverse.mockk 13 | quarkus-junit5-mockk 14 | Quarkus - JUnit 5 - Mockk 15 | 16 | 17 | 18 | org.jetbrains.kotlin 19 | kotlin-stdlib-jdk8 20 | ${kotlin.version} 21 | 22 | 23 | org.jetbrains.kotlin 24 | kotlin-reflect 25 | ${kotlin.version} 26 | 27 | 28 | io.quarkus 29 | quarkus-junit5 30 | 31 | 32 | io.quarkus 33 | quarkus-arc-deployment 34 | 35 | 36 | io.mockk 37 | mockk-jvm 38 | ${mockk.version} 39 | 40 | 41 | io.quarkus 42 | quarkus-rest-client 43 | test 44 | 45 | 46 | org.assertj 47 | assertj-core 48 | ${assertj.version} 49 | test 50 | 51 | 52 | io.quarkus 53 | quarkus-mutiny 54 | test 55 | 56 | 57 | 58 | 59 | src/main/kotlin 60 | src/test/kotlin 61 | 62 | 63 | org.jetbrains.kotlin 64 | kotlin-maven-plugin 65 | ${kotlin.version} 66 | 67 | 68 | compile 69 | 70 | compile 71 | 72 | 73 | 74 | test-compile 75 | 76 | test-compile 77 | 78 | 79 | 80 | 81 | 82 | org.jetbrains.dokka 83 | dokka-maven-plugin 84 | ${dokka.version} 85 | 86 | 87 | package 88 | 89 | javadocJar 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /junit5-mockk/src/main/kotlin/io/quarkiverse/test/junit/mockk/InjectMock.kt: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.test.junit.mockk 2 | 3 | /** 4 | * When used on a field of a test class, the field becomes a Mockk mock, 5 | * that is then used to mock the normal scoped bean which the field represents 6 | * 7 | * If {@code convertScopes} is set to true, then Quarkus will change the scope of the target {@code Singleton} bean to {@code ApplicationScoped} 8 | * to make the mockable. 9 | * This is an advanced setting and should only be used if you don't rely on the differences between {@code Singleton} 10 | * and {@code ApplicationScoped} beans (for example it is invalid to read fields of {@code ApplicationScoped} beans 11 | * as a proxy stands in place of the actual implementation) 12 | */ 13 | @Target(AnnotationTarget.FIELD) 14 | @Retention(AnnotationRetention.RUNTIME) 15 | annotation class InjectMock(val relaxed: Boolean = false, val relaxUnitFun: Boolean = false, 16 | val convertScopes: Boolean = false) 17 | -------------------------------------------------------------------------------- /junit5-mockk/src/main/kotlin/io/quarkiverse/test/junit/mockk/InjectSpy.kt: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.test.junit.mockk 2 | 3 | /** 4 | * When used on a field of a test class, the field becomes a Mockk spy, 5 | * that is then used to spy on the normal scoped bean which the field represents 6 | */ 7 | @kotlin.annotation.Target(AnnotationTarget.FIELD) 8 | @kotlin.annotation.Retention(AnnotationRetention.RUNTIME) 9 | annotation class InjectSpy() 10 | -------------------------------------------------------------------------------- /junit5-mockk/src/main/kotlin/io/quarkiverse/test/junit/mockk/internal/CreateMockkMocksCallback.kt: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.test.junit.mockk.internal 2 | 3 | import io.mockk.mockkClass 4 | import io.quarkiverse.test.junit.mockk.InjectMock 5 | import io.quarkus.arc.ClientProxy 6 | 7 | import io.quarkus.test.junit.callback.QuarkusTestAfterConstructCallback 8 | import java.lang.reflect.Field 9 | import kotlin.jvm.internal.Reflection 10 | 11 | class CreateMockkMocksCallback: QuarkusTestAfterConstructCallback { 12 | override fun afterConstruct(testInstance: Any?): Unit { 13 | testInstance?.let{ instance -> 14 | var current = instance::class.java 15 | while(current.superclass != null) { 16 | for (field in current.declaredFields) { 17 | if (field.isAnnotationPresent(InjectMock::class.java)) { 18 | val annotation = field.getAnnotation(InjectMock::class.java) 19 | val beanInstance = ReflectionUtils.getBeanInstance(testInstance, field, InjectMock::class.java) 20 | val mock = createAndSetMock(testInstance, field, beanInstance, annotation) 21 | MocksTracker.track(testInstance, mock, beanInstance) 22 | } 23 | } 24 | current = current.superclass 25 | } 26 | } 27 | } 28 | 29 | private fun createAndSetMock(testInstance: Any, field: Field, beanInstance: Any, annotation: InjectMock): Any { 30 | var beanClass = beanInstance::class 31 | if (ClientProxy::class.java.isAssignableFrom(beanClass.java)) { 32 | if (beanClass.java.superclass != Object::class.java) { 33 | beanClass = Reflection.createKotlinClass(beanClass.java.superclass) 34 | } 35 | } 36 | val mock = mockkClass(beanClass, relaxed = annotation.relaxed, relaxUnitFun = annotation.relaxUnitFun) 37 | if (field.trySetAccessible()) { 38 | field.set(testInstance, mock) 39 | } 40 | return mock 41 | } 42 | } -------------------------------------------------------------------------------- /junit5-mockk/src/main/kotlin/io/quarkiverse/test/junit/mockk/internal/CreateMockkSpiesCallback.kt: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.test.junit.mockk.internal 2 | 3 | import io.mockk.spyk 4 | import io.quarkiverse.test.junit.mockk.InjectSpy 5 | import io.quarkus.arc.ClientProxy 6 | import io.quarkus.test.junit.callback.QuarkusTestAfterConstructCallback 7 | import java.lang.reflect.Field 8 | 9 | class CreateMockkSpiesCallback: QuarkusTestAfterConstructCallback { 10 | override fun afterConstruct(testInstance: Any?) { 11 | testInstance?.let{ instance -> 12 | var current = instance::class.java 13 | while(current.superclass != null) { 14 | for (field in current.declaredFields) { 15 | if (field.isAnnotationPresent(InjectSpy::class.java)) { 16 | val beanInstance = ReflectionUtils.getBeanInstance(testInstance, field, InjectSpy::class.java) 17 | val spy = createSpyAndSetTestField(testInstance, field, beanInstance) 18 | MocksTracker.track(testInstance, spy, beanInstance) 19 | } 20 | } 21 | current = current.superclass 22 | } 23 | } 24 | } 25 | 26 | private fun createSpyAndSetTestField(testInstance: Any, field: Field, beanInstance: Any): Any { 27 | val spy = spyk(ClientProxy.unwrap(beanInstance)) 28 | if (field.trySetAccessible()) { 29 | field.set(testInstance, spy) 30 | } 31 | return spy 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /junit5-mockk/src/main/kotlin/io/quarkiverse/test/junit/mockk/internal/MocksTracker.kt: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.test.junit.mockk.internal 2 | 3 | import io.mockk.clearMocks 4 | import java.util.concurrent.ConcurrentHashMap 5 | 6 | object MocksTracker { 7 | 8 | private val usedMocks = ConcurrentHashMap>() 9 | 10 | fun track(testInstance: Any, mock: Any, beanInstance: Any) { 11 | usedMocks.computeIfAbsent(testInstance) { mutableSetOf() }.add(Mocked(mock, beanInstance)) 12 | } 13 | 14 | fun getMocks(testInstance: Any): MutableSet = usedMocks.getOrDefault(testInstance, mutableSetOf()) 15 | 16 | fun reset(testInstance: Any) { 17 | getMocks(testInstance).map{it.mock}.forEach(::clearMocks) 18 | } 19 | 20 | class Mocked(val mock: Any, val beanInstance: Any) 21 | } -------------------------------------------------------------------------------- /junit5-mockk/src/main/kotlin/io/quarkiverse/test/junit/mockk/internal/ReflectionUtils.kt: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.test.junit.mockk.internal 2 | 3 | import io.quarkiverse.test.junit.mockk.InjectMock 4 | import io.quarkiverse.test.junit.mockk.InjectSpy 5 | import io.quarkus.arc.Arc 6 | import java.lang.IllegalStateException 7 | import java.lang.reflect.Field 8 | import jakarta.inject.Qualifier 9 | 10 | object ReflectionUtils { 11 | 12 | fun getBeanInstance(testInstance: Any, field: Field, annotationType: Class): Any { 13 | val fieldClass = field.type 14 | return Arc.container().instance(fieldClass, *getQualifier(field))?.get() ?: throw IllegalStateException( 15 | "Invalid use of ${annotationType.typeName} - could not determine bean of type: $fieldClass. Offending field is ${field.name} of test class " 16 | + testInstance.javaClass 17 | ) 18 | } 19 | 20 | internal fun getQualifier(fieldToMock: Field): Array = fieldToMock.declaredAnnotations 21 | .filterNot(::isLibAnnotation) 22 | .filter { annotation -> annotation.annotationClass.java.annotations.any { subannotation -> subannotation.annotationClass.java == Qualifier::class.java } } 23 | .toTypedArray() 24 | 25 | private fun isLibAnnotation(annotation: Annotation): Boolean = annotation.annotationClass.java == InjectMock::class.java || annotation.annotationClass.java == InjectSpy::class.java 26 | } -------------------------------------------------------------------------------- /junit5-mockk/src/main/kotlin/io/quarkiverse/test/junit/mockk/internal/ResetMockkMocksCallback.kt: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.test.junit.mockk.internal 2 | 3 | import io.quarkus.test.junit.callback.QuarkusTestAfterEachCallback 4 | import io.quarkus.test.junit.callback.QuarkusTestMethodContext 5 | 6 | class ResetMockkMocksCallback: QuarkusTestAfterEachCallback { 7 | override fun afterEach(context: QuarkusTestMethodContext?) { 8 | context?.let { MocksTracker.reset(it.testInstance) } 9 | } 10 | } -------------------------------------------------------------------------------- /junit5-mockk/src/main/kotlin/io/quarkiverse/test/junit/mockk/internal/ResetOuterMockkMockCallback.kt: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.test.junit.mockk.internal 2 | 3 | import io.quarkus.test.junit.callback.QuarkusTestAfterAllCallback 4 | import io.quarkus.test.junit.callback.QuarkusTestContext 5 | 6 | class ResetOuterMockkMockCallback: QuarkusTestAfterAllCallback { 7 | 8 | override fun afterAll(context: QuarkusTestContext?) { 9 | context?.outerInstances?.map { 10 | MocksTracker.reset(it) 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /junit5-mockk/src/main/kotlin/io/quarkiverse/test/junit/mockk/internal/SetMockkMockAsBeanMockCallback.kt: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.test.junit.mockk.internal 2 | 3 | import io.quarkus.test.junit.QuarkusMock 4 | import io.quarkus.test.junit.callback.QuarkusTestBeforeEachCallback 5 | import io.quarkus.test.junit.callback.QuarkusTestMethodContext 6 | 7 | class SetMockkMockAsBeanMockCallback: QuarkusTestBeforeEachCallback { 8 | 9 | override fun beforeEach(context: QuarkusTestMethodContext?) { 10 | context?.outerInstances?.map { 11 | MocksTracker.getMocks(it).forEach(::installMock) 12 | } 13 | context?.let {MocksTracker.getMocks(it.testInstance).forEach(::installMock)} 14 | } 15 | 16 | private fun installMock(mocked: MocksTracker.Mocked) { 17 | try { 18 | QuarkusMock.installMockForInstance(mocked.mock, mocked.beanInstance); 19 | } catch (e: Exception) { 20 | throw RuntimeException("""$mocked.beanInstance 21 | is not a normal scoped CDI bean, make sure the bean is a normal scope like @ApplicationScoped or @RequestScoped. 22 | Alternatively you can use '@InjectMock(convertScopes=true)' instead of '@InjectMock' if you would like 23 | Quarkus to automatically make that conversion (you should only use this if you understand the implications).""".trimIndent()); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /junit5-mockk/src/main/kotlin/io/quarkiverse/test/junit/mockk/internal/SingletonToApplicationScopedTestBuildChainCustomizerProducer.kt: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.test.junit.mockk.internal 2 | 3 | import io.quarkiverse.test.junit.mockk.InjectMock 4 | import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem 5 | import io.quarkus.arc.processor.AnnotationsTransformer 6 | import io.quarkus.arc.processor.DotNames 7 | import io.quarkus.builder.BuildChainBuilder 8 | import io.quarkus.builder.BuildStep 9 | import io.quarkus.test.junit.buildchain.TestBuildChainCustomizerProducer 10 | import org.jboss.jandex.AnnotationTarget 11 | import org.jboss.jandex.AnnotationValue 12 | import org.jboss.jandex.ClassInfo 13 | import org.jboss.jandex.DotName 14 | import org.jboss.jandex.Index 15 | import java.util.function.Consumer 16 | 17 | class SingletonToApplicationScopedTestBuildChainCustomizerProducer: TestBuildChainCustomizerProducer { 18 | 19 | companion object { 20 | val INJECT_MOCK: DotName = DotName.createSimple(InjectMock::class.java.name) 21 | } 22 | 23 | override fun produce(testClassesIndex: Index): Consumer? = Consumer { buildChainBuilder -> 24 | buildChainBuilder?.let {it.addBuildStep(BuildStep { context -> 25 | val mockTypes = testClassesIndex?.getAnnotations(INJECT_MOCK) 26 | ?.filter { it.target().kind() == AnnotationTarget.Kind.FIELD } 27 | ?.filter { 28 | val allowScopeConversionValue = it.value("convertScopes") 29 | allowScopeConversionValue != null && allowScopeConversionValue.asBoolean() 30 | } 31 | ?.map { it.target().asField().type().name() } 32 | ?.toSet() ?: setOf() 33 | 34 | if (mockTypes.isNotEmpty()) { 35 | context.produce(AnnotationsTransformerBuildItem(object : AnnotationsTransformer { 36 | override fun appliesTo(kind: AnnotationTarget.Kind) = kind == AnnotationTarget.Kind.CLASS 37 | || kind == AnnotationTarget.Kind.METHOD 38 | 39 | override fun transform(transformationContext: AnnotationsTransformer.TransformationContext) { 40 | val target = transformationContext.target 41 | if (target.kind() == AnnotationTarget.Kind.CLASS) { // scope on bean case 42 | val classInfo = target.asClass() 43 | if (isMatchingBean(classInfo)) { 44 | if (classInfo.classAnnotation(DotNames.SINGLETON) != null) { 45 | replaceSingletonWithApplicationScoped(transformationContext) 46 | } 47 | } 48 | } else if (target.kind() == AnnotationTarget.Kind.METHOD) { // CDI producer case 49 | val methodInfo = target.asMethod() 50 | if (methodInfo.annotation(DotNames.PRODUCES) != null 51 | && methodInfo.annotation(DotNames.SINGLETON) != null 52 | ) { 53 | val returnType = methodInfo.returnType().name() 54 | if (mockTypes.contains(returnType)) { 55 | replaceSingletonWithApplicationScoped(transformationContext) 56 | } 57 | } 58 | } 59 | } 60 | 61 | private fun replaceSingletonWithApplicationScoped(transformationContext: AnnotationsTransformer.TransformationContext) { 62 | transformationContext.transform() 63 | .remove { it.name() == DotNames.SINGLETON } 64 | .add(DotNames.APPLICATION_SCOPED) 65 | .done() 66 | } 67 | 68 | // this is very simplistic and is the main reason why the annotation transformer strategy 69 | // is fine with most cases, but it can't cover all cases 70 | private fun isMatchingBean(classInfo: ClassInfo): Boolean { 71 | // class type matches 72 | if (mockTypes.contains(classInfo.name())) { 73 | return true 74 | } 75 | if (mockTypes.contains(classInfo.superName())) { 76 | return true 77 | } 78 | for (iface in classInfo.interfaceNames()) { 79 | if (mockTypes.contains(iface)) { 80 | return true 81 | } 82 | } 83 | return false 84 | } 85 | })) 86 | } 87 | })}?.produces(AnnotationsTransformerBuildItem::class.java)?.build() 88 | } 89 | } -------------------------------------------------------------------------------- /junit5-mockk/src/main/kotlin/io/quarkiverse/test/junit/mockk/internal/UnremoveableMockTestBuildChainCustomizerProducer.kt: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.test.junit.mockk.internal 2 | 3 | import io.quarkiverse.test.junit.mockk.InjectMock 4 | import io.quarkus.arc.deployment.UnremovableBeanBuildItem 5 | import io.quarkus.arc.deployment.UnremovableBeanBuildItem.BeanClassNamesExclusion 6 | import io.quarkus.builder.BuildChainBuilder 7 | import io.quarkus.test.junit.buildchain.TestBuildChainCustomizerProducer 8 | import org.jboss.jandex.DotName 9 | import org.jboss.jandex.Index 10 | import java.util.function.Consumer 11 | 12 | class UnremoveableMockTestBuildChainCustomizerProducer: TestBuildChainCustomizerProducer { 13 | 14 | companion object { 15 | val INJECT_MOCK = DotName.createSimple(InjectMock::class.java.name) 16 | } 17 | override fun produce(testClassesIndex: Index): Consumer? = Consumer { buildChainBuilder -> 18 | buildChainBuilder?.addBuildStep { context -> 19 | val mockTypes = testClassesIndex 20 | .getAnnotations(INJECT_MOCK) 21 | ?.map { it.target().asField().type().name().toString() } 22 | ?.toSet() 23 | context.produce( 24 | UnremovableBeanBuildItem(BeanClassNamesExclusion(mockTypes)) 25 | ) 26 | }?.produces(UnremovableBeanBuildItem::class.java)?.build() 27 | } 28 | } -------------------------------------------------------------------------------- /junit5-mockk/src/main/resources/META-INF/services/io.quarkus.test.junit.buildchain.TestBuildChainCustomizerProducer: -------------------------------------------------------------------------------- 1 | io.quarkiverse.test.junit.mockk.internal.UnremoveableMockTestBuildChainCustomizerProducer 2 | io.quarkiverse.test.junit.mockk.internal.SingletonToApplicationScopedTestBuildChainCustomizerProducer 3 | -------------------------------------------------------------------------------- /junit5-mockk/src/main/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestAfterAllCallback: -------------------------------------------------------------------------------- 1 | io.quarkiverse.test.junit.mockk.internal.ResetOuterMockkMockCallback -------------------------------------------------------------------------------- /junit5-mockk/src/main/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestAfterConstructCallback: -------------------------------------------------------------------------------- 1 | io.quarkiverse.test.junit.mockk.internal.CreateMockkMocksCallback 2 | io.quarkiverse.test.junit.mockk.internal.CreateMockkSpiesCallback 3 | -------------------------------------------------------------------------------- /junit5-mockk/src/main/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestAfterEachCallback: -------------------------------------------------------------------------------- 1 | io.quarkiverse.test.junit.mockk.internal.ResetMockkMocksCallback 2 | -------------------------------------------------------------------------------- /junit5-mockk/src/main/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestBeforeEachCallback: -------------------------------------------------------------------------------- 1 | io.quarkiverse.test.junit.mockk.internal.SetMockkMockAsBeanMockCallback 2 | -------------------------------------------------------------------------------- /junit5-mockk/src/test/kotlin/io/quarkiverse/test/junit/mockk/internal/ReflectionUtilsTest.kt: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.test.junit.mockk.internal 2 | 3 | import io.quarkiverse.test.junit.mockk.InjectMock 4 | import org.assertj.core.api.Assertions.assertThat 5 | import org.eclipse.microprofile.rest.client.inject.RestClient 6 | import org.junit.jupiter.api.Test 7 | import java.lang.reflect.Field 8 | 9 | internal class ReflectionUtilsTest { 10 | 11 | @Test 12 | fun `should find qualifier annotation`() { 13 | val c = TestClassWithBeans::class.java 14 | val restClientField: Field = c.declaredFields.first { it.isAnnotationPresent(RestClient::class.java) } 15 | 16 | val qualifiers = ReflectionUtils.getQualifier(restClientField) 17 | 18 | assertThat(qualifiers).hasSize(1) 19 | assertThat(qualifiers[0].annotationClass.java).isEqualTo(RestClient::class.java) 20 | } 21 | 22 | 23 | class TestClassWithBeans { 24 | 25 | @InjectMock 26 | @RestClient 27 | lateinit var rest: String 28 | } 29 | } -------------------------------------------------------------------------------- /junit5-mockk/src/test/kotlin/io/quarkiverse/test/junit/mockk/internal/application/SimpleSingletonBean.kt: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.test.junit.mockk.internal.application 2 | 3 | import jakarta.inject.Singleton 4 | 5 | @Singleton 6 | open class SimpleSingletonBean { 7 | 8 | fun greet(name: String) = "hello $name" 9 | 10 | } -------------------------------------------------------------------------------- /junit5-mockk/src/test/kotlin/io/quarkiverse/test/junit/mockk/internal/example/InjectRelaxedMockTest.kt: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.test.junit.mockk.internal.example 2 | 3 | import io.mockk.verify 4 | import io.quarkiverse.test.junit.mockk.InjectMock 5 | import io.quarkus.test.junit.QuarkusTest 6 | import org.junit.jupiter.api.Test 7 | import jakarta.enterprise.context.ApplicationScoped 8 | import jakarta.inject.Inject 9 | 10 | @QuarkusTest 11 | class InjectRelaxedMockTest { 12 | 13 | @Inject 14 | lateinit var firstService: FirstService 15 | 16 | @InjectMock(relaxed = true) 17 | lateinit var secondService: SecondService 18 | 19 | 20 | @Test 21 | fun `should run unit function`() { 22 | firstService.run() 23 | 24 | verify { secondService.run() } 25 | } 26 | 27 | @ApplicationScoped 28 | open class FirstService(val secondService: SecondService) { 29 | fun run(): Unit = secondService.run() 30 | } 31 | 32 | @ApplicationScoped 33 | open class SecondService { 34 | fun run(): Unit = println("run ran") 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /junit5-mockk/src/test/kotlin/io/quarkiverse/test/junit/mockk/internal/example/InjectRestClientTest.kt: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.test.junit.mockk.internal.example 2 | 3 | import io.mockk.every 4 | import io.mockk.verify 5 | import io.quarkiverse.test.junit.mockk.InjectMock 6 | import io.quarkus.test.junit.QuarkusTest 7 | import org.assertj.core.api.Assertions.assertThat 8 | import org.eclipse.microprofile.rest.client.inject.RegisterRestClient 9 | import org.eclipse.microprofile.rest.client.inject.RestClient 10 | import org.junit.jupiter.api.Test 11 | import jakarta.enterprise.context.ApplicationScoped 12 | import jakarta.inject.Inject 13 | import jakarta.ws.rs.GET 14 | import jakarta.ws.rs.Path 15 | import jakarta.ws.rs.PathParam 16 | import jakarta.ws.rs.Produces 17 | import jakarta.ws.rs.core.MediaType 18 | 19 | @QuarkusTest 20 | class InjectRestClientTest { 21 | 22 | @Inject 23 | lateinit var greeterService: GreeterService 24 | 25 | @InjectMock 26 | @RestClient 27 | lateinit var serviceClient: ServiceClient 28 | 29 | @Test 30 | fun `should use mocked serviceClient`() { 31 | every { serviceClient.getByName(any()) } returns "Quarkus" 32 | 33 | assertThat(greeterService.greet("quarkus")).isEqualTo("Hello Quarkus") 34 | 35 | verify { serviceClient.getByName("quarkus") } 36 | } 37 | 38 | 39 | @ApplicationScoped 40 | class GreeterService(@RestClient val serviceClient: ServiceClient) { 41 | fun greet(name: String) = "Hello ${serviceClient.getByName(name)}" 42 | } 43 | 44 | @ApplicationScoped 45 | @Path("/v2") 46 | @RegisterRestClient(configKey = "restclient.ServiceClient") 47 | interface ServiceClient { 48 | 49 | @GET 50 | @Path("/name/{name}") 51 | @Produces(MediaType.APPLICATION_JSON) 52 | fun getByName(@PathParam("name") name: String): String 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /junit5-mockk/src/test/kotlin/io/quarkiverse/test/junit/mockk/internal/example/InjectionMockTest.kt: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.test.junit.mockk.internal.example 2 | 3 | import io.mockk.every 4 | import io.mockk.verify 5 | import io.quarkiverse.test.junit.mockk.InjectMock 6 | import io.quarkus.test.junit.QuarkusTest 7 | import org.assertj.core.api.Assertions.assertThat 8 | import org.junit.jupiter.api.Test 9 | import jakarta.enterprise.context.ApplicationScoped 10 | import jakarta.inject.Inject 11 | 12 | @QuarkusTest 13 | class InjectionMockTest { 14 | 15 | @Inject 16 | lateinit var firstService: FirstService 17 | 18 | @InjectMock 19 | lateinit var secondService: SecondService 20 | 21 | @Test 22 | fun `should respond test`() { 23 | every { secondService.greet() } returns "test" 24 | assertThat(firstService.greet()).isEqualTo("test") 25 | } 26 | 27 | @Test 28 | fun `should respond second`() { 29 | every { secondService.greet() } returns "second" 30 | assertThat(firstService.greet()).isEqualTo("second") 31 | verify { secondService.greet() } 32 | } 33 | 34 | 35 | @ApplicationScoped 36 | open class FirstService(val secondService: SecondService) { 37 | fun greet(): String = secondService.greet() 38 | } 39 | 40 | @ApplicationScoped 41 | open class SecondService { 42 | fun greet(): String = "Second service" 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /junit5-mockk/src/test/kotlin/io/quarkiverse/test/junit/mockk/internal/example/InjectionSpyTest.kt: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.test.junit.mockk.internal.example 2 | 3 | import io.mockk.every 4 | import io.quarkiverse.test.junit.mockk.InjectSpy 5 | import io.quarkus.test.junit.QuarkusTest 6 | import org.assertj.core.api.Assertions.assertThat 7 | import org.junit.jupiter.api.Test 8 | import jakarta.enterprise.context.ApplicationScoped 9 | import jakarta.inject.Inject 10 | 11 | @QuarkusTest 12 | class InjectionSpyTest { 13 | 14 | @Inject 15 | lateinit var firstService: FirstService 16 | 17 | @InjectSpy 18 | lateinit var secondService: SecondService 19 | 20 | @Test 21 | fun `should return normal response`() { 22 | assertThat(firstService.greet()).isEqualTo("Second service") 23 | } 24 | 25 | @Test 26 | fun `should respond second`() { 27 | every { secondService.greet() } returns "second" 28 | assertThat(firstService.greet()).isEqualTo("second") 29 | } 30 | 31 | 32 | @ApplicationScoped 33 | open class FirstService(val secondService: SecondService) { 34 | fun greet(): String= secondService.greet() 35 | } 36 | 37 | @ApplicationScoped 38 | open class SecondService { 39 | fun greet(): String = "Second service" 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /junit5-mockk/src/test/kotlin/io/quarkiverse/test/junit/mockk/internal/example/ReturnMutinyTypeTest.kt: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.test.junit.mockk.internal.example 2 | 3 | import io.mockk.every 4 | import io.quarkiverse.test.junit.mockk.InjectMock 5 | import io.quarkus.test.junit.QuarkusTest 6 | import io.smallrye.mutiny.Uni 7 | import org.assertj.core.api.Assertions.assertThat 8 | import org.junit.jupiter.api.Test 9 | import jakarta.enterprise.context.ApplicationScoped 10 | 11 | @QuarkusTest 12 | class ReturnMutinyTypeTest { 13 | 14 | @InjectMock lateinit var service: Service 15 | 16 | @Test 17 | fun `should mock mutiny type`() { 18 | every { service.greet() } returns Uni.createFrom().item("mock") 19 | 20 | val resp = service.greet().await().indefinitely() 21 | assertThat(resp).isEqualTo("mock") 22 | } 23 | 24 | 25 | @ApplicationScoped 26 | open class Service() { 27 | fun greet(): Uni = Uni.createFrom().item("test") 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /junit5-mockk/src/test/resources/application.properties: -------------------------------------------------------------------------------- 1 | restclient.ServiceClient/mp-rest/url=https://fakeservice.eu/rest -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | io.quarkiverse 7 | quarkiverse-parent 8 | 18 9 | 10 | 11 | io.quarkiverse.mockk 12 | quarkus-junit5-mockk-parent 13 | pom 14 | 999-SNAPSHOT 15 | Quarkus - JUnit 5 - Mockk Parent 16 | 17 | 18 | 1.9.22 19 | 1.13.12 20 | 3.13.1 21 | 1.9.20 22 | 3.26.3 23 | 24 | 25 | true 26 | 17 27 | 28 | 29 | 30 | scm:git:git@github.com:quarkiverse/quarkus-mockk.git 31 | scm:git:git@github.com:quarkiverse/quarkus-mockk.git 32 | https://github.com/quarkiverse/quarkus-mockk 33 | HEAD 34 | 35 | 36 | 37 | 38 | 39 | io.quarkus 40 | quarkus-bom 41 | ${quarkus.version} 42 | pom 43 | import 44 | 45 | 46 | 47 | 48 | 49 | junit5-mockk 50 | 51 | 52 | 53 | 54 | docs 55 | 56 | 57 | performRelease 58 | !true 59 | 60 | 61 | 62 | docs 63 | 64 | 65 | 66 | it 67 | 68 | 69 | performRelease 70 | !true 71 | 72 | 73 | 74 | integration-tests 75 | 76 | 77 | 78 | 79 | --------------------------------------------------------------------------------