├── .github └── workflows │ ├── create-diagram.yml │ └── gradle.yml ├── .gitignore ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── LICENSE ├── README.md ├── build.gradle ├── diagram.svg ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── mvnw ├── mvnw.cmd ├── pom.xml ├── settings.gradle └── src ├── main └── java │ └── com │ └── kousenit │ ├── astro │ ├── Assignment.java │ ├── AstroGatewayHttpClient.java │ ├── AstroGatewayRetrofit.java │ ├── AstroResponse.java │ ├── AstroService.java │ ├── Gateway.java │ └── astro_data.json │ ├── hr │ ├── DefaultTranslationService.java │ ├── HelloMockito.java │ ├── InMemoryPersonRepository.java │ ├── Person.java │ ├── PersonRepository.java │ ├── PersonService.java │ └── TranslationService.java │ ├── inorder │ ├── OrderService.java │ ├── PaymentProcessor.java │ ├── PaymentService.java │ └── ShippingService.java │ ├── pubsub │ ├── Publisher.java │ └── Subscriber.java │ ├── simple │ ├── AddingMachine.java │ ├── HelloService.java │ ├── LoggingDemo.java │ └── TranslateService.java │ └── wikipedia │ ├── BioService.java │ ├── WikiPage.java │ ├── WikiQuery.java │ ├── WikiResponse.java │ └── WikiUtil.java └── test ├── java └── com │ └── kousenit │ ├── astro │ ├── AssignmentTest.java │ ├── AstroGatewayHttpClientTest.java │ ├── AstroGatewayRetrofitTest.java │ ├── AstroServiceTest.java │ └── FakeGateway.java │ ├── hr │ ├── DefaultTranslationServiceTest.java │ ├── HelloMockitoTest.java │ ├── InMemoryPersonRepositoryTest.java │ ├── PersonServiceBDDTest.java │ ├── PersonServiceTest.java │ └── PersonTest.java │ ├── inorder │ ├── OrderServiceTest.java │ └── PaymentServiceTest.java │ ├── pubsub │ └── PublisherTest.java │ ├── simple │ ├── AddingMachineAnnotationTest.java │ ├── AddingMachineJUnit5Test.java │ ├── AddingMachineTest.java │ ├── DocsTest.java │ ├── HelloServiceTest.java │ ├── LoggingDemoTests.java │ ├── LoggingStandardErrorTests.java │ ├── LoggingWithOutputCaptureTest.java │ ├── MockFinalTypesTests.java │ └── MockListOfInteger.java │ └── wikipedia │ ├── BioServiceTest.java │ ├── WikiPageTest.java │ ├── WikiResponseTest.java │ └── WikiUtilTest.java └── resources ├── junit-platform.properties └── mappings └── response.json /.github/workflows/create-diagram.yml: -------------------------------------------------------------------------------- 1 | name: Create diagram 2 | on: 3 | workflow_dispatch: {} 4 | push: 5 | branches: 6 | - main 7 | jobs: 8 | get_data: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout code 12 | uses: actions/checkout@master 13 | - name: Update diagram 14 | uses: githubocto/repo-visualizer@main 15 | with: 16 | excluded_paths: "ignore,.github" -------------------------------------------------------------------------------- /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | name: Java CI 2 | on: [ push ] 3 | jobs: 4 | build: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v2 8 | - name: Set up JDK 17 9 | uses: actions/setup-java@v2 10 | with: 11 | distribution: 'temurin' 12 | java-version: 17 13 | - uses: gradle/gradle-build-action@v2.4.2 14 | with: 15 | arguments: build 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | target 3 | bin 4 | classes 5 | .idea 6 | .gradle 7 | .project 8 | .classpath 9 | .settings 10 | out 11 | *.iml 12 | *.ipr 13 | *.iws 14 | .jqwik-database 15 | .DS_Store -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kousen/mockitobook/bb53e9cc29a98b50d355eac8d38620ebcc1ed6fd/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.0/apache-maven-3.9.0-bin.zip 18 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Ken Kousen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mockitobook 2 | Code examples for the book _Mockito Made Clear_, 3 | published by Pragmatic Programmers. 4 | 5 | See [the book page](https://pragprog.com/titles/mockito/mockito-made-clear/) for more information. 6 | 7 | ![Visualization of this repo](./diagram.svg) 8 | 9 | You can check out the whole GitHub Action at [diagram.yml](/.github/workflows/diagram.yml). Notice that we're excluding the `ignore` and `.github` folders, using the `excluded_paths` config. 10 | 11 | ## Running the code 12 | To run the code, use the included Gradle wrapper (the `gradlew` scripts for Un*x and Windows) and execute either the `build` or `test` tasks. You can also run individual tests via Gradle, or just load them into your preferred IDE. 13 | 14 | This project uses [Gradle version catalogs](https://docs.gradle.org/current/userguide/platforms.html#sub:central-declaration-of-dependencies), which require Gradle 7.4 or higher. The included wrapper is higher than that. The dependency versions are inside the `libs.versions.toml` file in the `gradle` directory, which are used inside `build.gradle`. 15 | 16 | See also the Mockito play list at my [YouTube channel](https://www.youtube.com/@talesfromthejarside?sub_confirmation=1). -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Using Gradle version catalogs -- see gradle/libs.versions.toml 2 | plugins { 3 | id 'java' 4 | id 'jacoco' 5 | alias(libs.plugins.versions) 6 | alias(libs.plugins.version.catalog.update) 7 | } 8 | 9 | group 'com.kousenit' 10 | version '1.0' 11 | 12 | // Java 11+ needed for HttpClient 13 | java { 14 | toolchain { 15 | languageVersion.set(JavaLanguageVersion.of(17)) 16 | } 17 | } 18 | 19 | repositories { 20 | mavenCentral() 21 | } 22 | 23 | dependencies { 24 | // JUnit bundle (includes vintage engine) 25 | testImplementation libs.bundles.junit 26 | 27 | // Mockito bundle (inline and JUnit Jupiter engine) 28 | testImplementation libs.bundles.mockito 29 | 30 | // AssertJ 31 | testImplementation libs.assertj 32 | 33 | // Spring test library (don't move to 3.* unless Java 17+) 34 | testImplementation 'org.springframework.boot:spring-boot-test:3.2.5' 35 | 36 | // Retrofit 37 | implementation libs.retrofit.core 38 | implementation libs.retrofit.gson 39 | 40 | // Make security warning go away for retrofit transitive dep 41 | implementation 'com.squareup.okhttp3:okhttp:4.12.0' 42 | 43 | // Jackson JSON library 44 | implementation libs.jackson 45 | 46 | // Gson 47 | implementation libs.gson 48 | } 49 | 50 | tasks.named('test',Test) { 51 | useJUnitPlatform() 52 | maxParallelForks = (int) (Runtime.runtime.availableProcessors() / 2 + 1) 53 | } 54 | 55 | jacocoTestReport.dependsOn(test) -------------------------------------------------------------------------------- /diagram.svg: -------------------------------------------------------------------------------- 1 | srcsrcgradlegradle.mvn/wrapper.mvn/wrappertesttestmain/java/com/kousenitmain/java/com/kousenitwrapperwrapperresourcesresourcesjava/com/kousenitjava/com/kousenitwikipediawikipediasimplesimplepubsubpubsubinorderinorderhrhrastroastrowikipediawikipediasimplesimpleinorderinorderhrhrastroastromvnwmvnwmvnwmvnw.cmdmvnw.cmdmvnw.cmdgradlewgradlewgradlewmaven-wrapp...maven-wrapp...maven-wrapp...gradle-wrap...gradle-wrap...gradle-wrap...mappings...mappings...mappings...pubsub/P...pubsub/P...pubsub/P...AddingMa...AddingMa...AddingMa...MockList...MockList...MockList...PersonServic...PersonServic...PersonServic...HelloMocki...HelloMocki...HelloMocki...AstroServi...AstroServi...AstroServi....astro.bat.cmd.gitignore.gradle.java.json.md.properties.svg.toml.xmleach dot sized by file size -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.caching=true 2 | org.gradle.parallel=true -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | assertj = "3.25.3" 3 | gson = "2.10.1" 4 | jackson = "2.17.0" 5 | junit = "5.11.0-M1" 6 | junit-platform = "1.11.0-M1" 7 | mockito = "5.11.0" 8 | retrofit = "2.11.0" 9 | 10 | [libraries] 11 | assertj = { module = "org.assertj:assertj-core", version.ref = "assertj" } 12 | gson = { module = "com.google.code.gson:gson", version.ref = "gson" } 13 | jackson = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jackson" } 14 | junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" } 15 | junit-platform = { module = "org.junit.platform:junit-platform-launcher", version.ref = "junit-platform" } 16 | junit-vintage = { module = "org.junit.vintage:junit-vintage-engine", version.ref = "junit" } 17 | mockito-core = { module = "org.mockito:mockito-core", version.ref = "mockito" } 18 | mockito-junit = { module = "org.mockito:mockito-junit-jupiter", version.ref = "mockito" } 19 | retrofit-core = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" } 20 | retrofit-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "retrofit" } 21 | 22 | [bundles] 23 | junit = [ 24 | "junit-jupiter", 25 | "junit-platform", 26 | "junit-vintage", 27 | ] 28 | mockito = [ 29 | "mockito-core", 30 | "mockito-junit", 31 | ] 32 | 33 | [plugins] 34 | version-catalog-update = "nl.littlerobots.version-catalog-update:0.8.4" 35 | versions = "com.github.ben-manes.versions:0.51.0" 36 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kousen/mockitobook/bb53e9cc29a98b50d355eac8d38620ebcc1ed6fd/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MSYS* | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /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 | # Apache Maven Wrapper startup batch script, version 3.1.1 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | # e.g. to debug Maven itself, use 32 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | # ---------------------------------------------------------------------------- 35 | 36 | if [ -z "$MAVEN_SKIP_RC" ] ; then 37 | 38 | if [ -f /usr/local/etc/mavenrc ] ; then 39 | . /usr/local/etc/mavenrc 40 | fi 41 | 42 | if [ -f /etc/mavenrc ] ; then 43 | . /etc/mavenrc 44 | fi 45 | 46 | if [ -f "$HOME/.mavenrc" ] ; then 47 | . "$HOME/.mavenrc" 48 | fi 49 | 50 | fi 51 | 52 | # OS specific support. $var _must_ be set to either true or false. 53 | cygwin=false; 54 | darwin=false; 55 | mingw=false 56 | case "`uname`" in 57 | CYGWIN*) cygwin=true ;; 58 | MINGW*) mingw=true;; 59 | Darwin*) darwin=true 60 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 61 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 62 | if [ -z "$JAVA_HOME" ]; then 63 | if [ -x "/usr/libexec/java_home" ]; then 64 | JAVA_HOME="`/usr/libexec/java_home`"; export JAVA_HOME 65 | else 66 | JAVA_HOME="/Library/Java/Home"; export JAVA_HOME 67 | fi 68 | fi 69 | ;; 70 | esac 71 | 72 | if [ -z "$JAVA_HOME" ] ; then 73 | if [ -r /etc/gentoo-release ] ; then 74 | JAVA_HOME=`java-config --jre-home` 75 | fi 76 | fi 77 | 78 | # For Cygwin, ensure paths are in UNIX format before anything is touched 79 | if $cygwin ; then 80 | [ -n "$JAVA_HOME" ] && 81 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 82 | [ -n "$CLASSPATH" ] && 83 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 84 | fi 85 | 86 | # For Mingw, ensure paths are in UNIX format before anything is touched 87 | if $mingw ; then 88 | [ -n "$JAVA_HOME" ] && 89 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 90 | fi 91 | 92 | if [ -z "$JAVA_HOME" ]; then 93 | javaExecutable="`which javac`" 94 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 95 | # readlink(1) is not available as standard on Solaris 10. 96 | readLink=`which readlink` 97 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 98 | if $darwin ; then 99 | javaHome="`dirname \"$javaExecutable\"`" 100 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 101 | else 102 | javaExecutable="`readlink -f \"$javaExecutable\"`" 103 | fi 104 | javaHome="`dirname \"$javaExecutable\"`" 105 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 106 | JAVA_HOME="$javaHome" 107 | export JAVA_HOME 108 | fi 109 | fi 110 | fi 111 | 112 | if [ -z "$JAVACMD" ] ; then 113 | if [ -n "$JAVA_HOME" ] ; then 114 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 115 | # IBM's JDK on AIX uses strange locations for the executables 116 | JAVACMD="$JAVA_HOME/jre/sh/java" 117 | else 118 | JAVACMD="$JAVA_HOME/bin/java" 119 | fi 120 | else 121 | JAVACMD="`\\unset -f command; \\command -v java`" 122 | fi 123 | fi 124 | 125 | if [ ! -x "$JAVACMD" ] ; then 126 | echo "Error: JAVA_HOME is not defined correctly." >&2 127 | echo " We cannot execute $JAVACMD" >&2 128 | exit 1 129 | fi 130 | 131 | if [ -z "$JAVA_HOME" ] ; then 132 | echo "Warning: JAVA_HOME environment variable is not set." 133 | fi 134 | 135 | # traverses directory structure from process work directory to filesystem root 136 | # first directory with .mvn subdirectory is considered project base directory 137 | find_maven_basedir() { 138 | if [ -z "$1" ] 139 | then 140 | echo "Path not specified to find_maven_basedir" 141 | return 1 142 | fi 143 | 144 | basedir="$1" 145 | wdir="$1" 146 | while [ "$wdir" != '/' ] ; do 147 | if [ -d "$wdir"/.mvn ] ; then 148 | basedir=$wdir 149 | break 150 | fi 151 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 152 | if [ -d "${wdir}" ]; then 153 | wdir=`cd "$wdir/.."; pwd` 154 | fi 155 | # end of workaround 156 | done 157 | printf '%s' "$(cd "$basedir"; pwd)" 158 | } 159 | 160 | # concatenates all lines of a file 161 | concat_lines() { 162 | if [ -f "$1" ]; then 163 | echo "$(tr -s '\n' ' ' < "$1")" 164 | fi 165 | } 166 | 167 | BASE_DIR=$(find_maven_basedir "$(dirname $0)") 168 | if [ -z "$BASE_DIR" ]; then 169 | exit 1; 170 | fi 171 | 172 | MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR 173 | if [ "$MVNW_VERBOSE" = true ]; then 174 | echo $MAVEN_PROJECTBASEDIR 175 | fi 176 | 177 | ########################################################################################## 178 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 179 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 180 | ########################################################################################## 181 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 182 | if [ "$MVNW_VERBOSE" = true ]; then 183 | echo "Found .mvn/wrapper/maven-wrapper.jar" 184 | fi 185 | else 186 | if [ "$MVNW_VERBOSE" = true ]; then 187 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 188 | fi 189 | if [ -n "$MVNW_REPOURL" ]; then 190 | wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" 191 | else 192 | wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" 193 | fi 194 | while IFS="=" read key value; do 195 | case "$key" in (wrapperUrl) wrapperUrl="$value"; break ;; 196 | esac 197 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 198 | if [ "$MVNW_VERBOSE" = true ]; then 199 | echo "Downloading from: $wrapperUrl" 200 | fi 201 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 202 | if $cygwin; then 203 | wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` 204 | fi 205 | 206 | if command -v wget > /dev/null; then 207 | QUIET="--quiet" 208 | if [ "$MVNW_VERBOSE" = true ]; then 209 | echo "Found wget ... using wget" 210 | QUIET="" 211 | fi 212 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 213 | wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" 214 | else 215 | wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" 216 | fi 217 | [ $? -eq 0 ] || rm -f "$wrapperJarPath" 218 | elif command -v curl > /dev/null; then 219 | QUIET="--silent" 220 | if [ "$MVNW_VERBOSE" = true ]; then 221 | echo "Found curl ... using curl" 222 | QUIET="" 223 | fi 224 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 225 | curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L 226 | else 227 | curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L 228 | fi 229 | [ $? -eq 0 ] || rm -f "$wrapperJarPath" 230 | else 231 | if [ "$MVNW_VERBOSE" = true ]; then 232 | echo "Falling back to using Java to download" 233 | fi 234 | javaSource="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 235 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" 236 | # For Cygwin, switch paths to Windows format before running javac 237 | if $cygwin; then 238 | javaSource=`cygpath --path --windows "$javaSource"` 239 | javaClass=`cygpath --path --windows "$javaClass"` 240 | fi 241 | if [ -e "$javaSource" ]; then 242 | if [ ! -e "$javaClass" ]; then 243 | if [ "$MVNW_VERBOSE" = true ]; then 244 | echo " - Compiling MavenWrapperDownloader.java ..." 245 | fi 246 | # Compiling the Java class 247 | ("$JAVA_HOME/bin/javac" "$javaSource") 248 | fi 249 | if [ -e "$javaClass" ]; then 250 | # Running the downloader 251 | if [ "$MVNW_VERBOSE" = true ]; then 252 | echo " - Running MavenWrapperDownloader.java ..." 253 | fi 254 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 255 | fi 256 | fi 257 | fi 258 | fi 259 | ########################################################################################## 260 | # End of extension 261 | ########################################################################################## 262 | 263 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 264 | 265 | # For Cygwin, switch paths to Windows format before running java 266 | if $cygwin; then 267 | [ -n "$JAVA_HOME" ] && 268 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 269 | [ -n "$CLASSPATH" ] && 270 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 271 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 272 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 273 | fi 274 | 275 | # Provide a "standardized" way to retrieve the CLI args that will 276 | # work with both Windows and non-Windows executions. 277 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 278 | export MAVEN_CMD_LINE_ARGS 279 | 280 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 281 | 282 | exec "$JAVACMD" \ 283 | $MAVEN_OPTS \ 284 | $MAVEN_DEBUG_OPTS \ 285 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 286 | "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 287 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 288 | -------------------------------------------------------------------------------- /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 Apache Maven Wrapper startup batch script, version 3.1.1 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 MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 28 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 29 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 30 | @REM e.g. to debug Maven itself, use 31 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 32 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 33 | @REM ---------------------------------------------------------------------------- 34 | 35 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 36 | @echo off 37 | @REM set title of command window 38 | title %0 39 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 40 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 41 | 42 | @REM set %HOME% to equivalent of $HOME 43 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 44 | 45 | @REM Execute a user defined script before this one 46 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 47 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 48 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 49 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* 50 | :skipRcPre 51 | 52 | @setlocal 53 | 54 | set ERROR_CODE=0 55 | 56 | @REM To isolate internal variables from possible post scripts, we use another setlocal 57 | @setlocal 58 | 59 | @REM ==== START VALIDATION ==== 60 | if not "%JAVA_HOME%" == "" goto OkJHome 61 | 62 | echo. 63 | echo Error: JAVA_HOME not found in your environment. >&2 64 | echo Please set the JAVA_HOME variable in your environment to match the >&2 65 | echo location of your Java installation. >&2 66 | echo. 67 | goto error 68 | 69 | :OkJHome 70 | if exist "%JAVA_HOME%\bin\java.exe" goto init 71 | 72 | echo. 73 | echo Error: JAVA_HOME is set to an invalid directory. >&2 74 | echo JAVA_HOME = "%JAVA_HOME%" >&2 75 | echo Please set the JAVA_HOME variable in your environment to match the >&2 76 | echo location of your Java installation. >&2 77 | echo. 78 | goto error 79 | 80 | @REM ==== END VALIDATION ==== 81 | 82 | :init 83 | 84 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 85 | @REM Fallback to current working directory if not found. 86 | 87 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 88 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 89 | 90 | set EXEC_DIR=%CD% 91 | set WDIR=%EXEC_DIR% 92 | :findBaseDir 93 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 94 | cd .. 95 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 96 | set WDIR=%CD% 97 | goto findBaseDir 98 | 99 | :baseDirFound 100 | set MAVEN_PROJECTBASEDIR=%WDIR% 101 | cd "%EXEC_DIR%" 102 | goto endDetectBaseDir 103 | 104 | :baseDirNotFound 105 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 106 | cd "%EXEC_DIR%" 107 | 108 | :endDetectBaseDir 109 | 110 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 111 | 112 | @setlocal EnableExtensions EnableDelayedExpansion 113 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 114 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 115 | 116 | :endReadAdditionalConfig 117 | 118 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 121 | 122 | set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" 123 | 124 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 125 | IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B 126 | ) 127 | 128 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 129 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 130 | if exist %WRAPPER_JAR% ( 131 | if "%MVNW_VERBOSE%" == "true" ( 132 | echo Found %WRAPPER_JAR% 133 | ) 134 | ) else ( 135 | if not "%MVNW_REPOURL%" == "" ( 136 | SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" 137 | ) 138 | if "%MVNW_VERBOSE%" == "true" ( 139 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 140 | echo Downloading from: %WRAPPER_URL% 141 | ) 142 | 143 | powershell -Command "&{"^ 144 | "$webclient = new-object System.Net.WebClient;"^ 145 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 146 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 147 | "}"^ 148 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ 149 | "}" 150 | if "%MVNW_VERBOSE%" == "true" ( 151 | echo Finished downloading %WRAPPER_JAR% 152 | ) 153 | ) 154 | @REM End of extension 155 | 156 | @REM Provide a "standardized" way to retrieve the CLI args that will 157 | @REM work with both Windows and non-Windows executions. 158 | set MAVEN_CMD_LINE_ARGS=%* 159 | 160 | %MAVEN_JAVA_EXE% ^ 161 | %JVM_CONFIG_MAVEN_PROPS% ^ 162 | %MAVEN_OPTS% ^ 163 | %MAVEN_DEBUG_OPTS% ^ 164 | -classpath %WRAPPER_JAR% ^ 165 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 166 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 167 | if ERRORLEVEL 1 goto error 168 | goto end 169 | 170 | :error 171 | set ERROR_CODE=1 172 | 173 | :end 174 | @endlocal & set ERROR_CODE=%ERROR_CODE% 175 | 176 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 177 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 178 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 179 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 180 | :skipRcPost 181 | 182 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 183 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 184 | 185 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 186 | 187 | cmd /C exit /B %ERROR_CODE% 188 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.kousenit 8 | mockitobook 9 | 1.0-SNAPSHOT 10 | 11 | 12 | 11 13 | 11 14 | UTF-8 15 | 16 | 17 | 18 | org.mockito 19 | mockito-core 20 | 5.4.0 21 | 22 | 23 | org.mockito 24 | mockito-junit-jupiter 25 | 5.4.0 26 | 27 | 28 | org.junit.jupiter 29 | junit-jupiter 30 | 5.9.2 31 | test 32 | 33 | 34 | org.junit.vintage 35 | junit-vintage-engine 36 | 5.9.2 37 | test 38 | 39 | 40 | org.assertj 41 | assertj-core 42 | 3.24.2 43 | test 44 | 45 | 46 | com.google.code.gson 47 | gson 48 | 2.10.1 49 | 50 | 51 | com.fasterxml.jackson.core 52 | jackson-databind 53 | 2.14.2 54 | 55 | 56 | com.squareup.retrofit2 57 | retrofit 58 | 2.9.0 59 | 60 | 61 | com.squareup.retrofit2 62 | converter-gson 63 | 2.9.0 64 | 65 | 66 | 67 | 68 | 69 | org.apache.maven.plugins 70 | maven-surefire-plugin 71 | 3.0.0-M8 72 | 73 | 74 | org.apache.maven.plugins 75 | maven-compiler-plugin 76 | 3.10.1 77 | 78 | 11 79 | 11 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.gradle.enterprise' version '3.8.1' 3 | } 4 | 5 | gradleEnterprise { 6 | buildScan { 7 | termsOfServiceUrl = "https://gradle.com/terms-of-service" 8 | termsOfServiceAgree = "yes" 9 | } 10 | } 11 | 12 | rootProject.name = 'mockitobook' 13 | -------------------------------------------------------------------------------- /src/main/java/com/kousenit/astro/Assignment.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.astro; 2 | 3 | public class Assignment { 4 | private final String name; 5 | private final String craft; 6 | 7 | public Assignment(String name, String craft) { 8 | this.name = name; 9 | this.craft = craft; 10 | } 11 | 12 | public String getCraft() { 13 | return craft; 14 | } 15 | 16 | public String getName() { 17 | return name; 18 | } 19 | 20 | @Override 21 | public String toString() { 22 | return "Assignment{" + 23 | "name='" + name + '\'' + 24 | ", craft='" + craft + '\'' + 25 | '}'; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/kousenit/astro/AstroGatewayHttpClient.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.astro; 2 | 3 | import com.google.gson.Gson; 4 | 5 | import java.io.IOException; 6 | import java.net.URI; 7 | import java.net.http.HttpClient; 8 | import java.net.http.HttpRequest; 9 | import java.net.http.HttpResponse; 10 | import java.time.Duration; 11 | 12 | @SuppressWarnings("HttpUrlsUsage") 13 | public class AstroGatewayHttpClient implements Gateway { 14 | private static final String DEFAULT_URL = "http://api.open-notify.org/"; 15 | private final String url; 16 | 17 | public AstroGatewayHttpClient() { 18 | this(DEFAULT_URL); 19 | } 20 | 21 | public AstroGatewayHttpClient(String url) { 22 | this.url = url; 23 | } 24 | 25 | @Override 26 | public AstroResponse getResponse() { 27 | HttpClient client = HttpClient.newHttpClient(); 28 | HttpRequest request = HttpRequest.newBuilder() 29 | .uri(URI.create(url + "astros.json")) 30 | .timeout(Duration.ofSeconds(2)) 31 | .build(); 32 | try { 33 | HttpResponse response = 34 | client.send(request, HttpResponse.BodyHandlers.ofString()); 35 | return new Gson().fromJson(response.body(), AstroResponse.class); 36 | } catch (IOException | InterruptedException e) { 37 | throw new RuntimeException(e); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/kousenit/astro/AstroGatewayRetrofit.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.astro; 2 | 3 | import retrofit2.Call; 4 | import retrofit2.Retrofit; 5 | import retrofit2.converter.gson.GsonConverterFactory; 6 | import retrofit2.http.GET; 7 | 8 | import java.io.IOException; 9 | 10 | @SuppressWarnings("HttpUrlsUsage") 11 | public class AstroGatewayRetrofit implements Gateway { 12 | private static final String DEFAULT_URL = "http://api.open-notify.org/"; 13 | private final String url; 14 | 15 | public AstroGatewayRetrofit() { 16 | this(DEFAULT_URL); 17 | } 18 | 19 | public AstroGatewayRetrofit(String url) { 20 | this.url = url; 21 | } 22 | 23 | @Override 24 | public AstroResponse getResponse() { 25 | Retrofit retrofit = new Retrofit.Builder() 26 | .baseUrl(url) 27 | .addConverterFactory(GsonConverterFactory.create()) 28 | .build(); 29 | 30 | OpenNotify openNotify = retrofit.create(OpenNotify.class); 31 | try { 32 | return openNotify.getAstronautsInSpace().execute().body(); 33 | } catch (IOException e) { 34 | throw new RuntimeException(e); 35 | } 36 | } 37 | 38 | interface OpenNotify { 39 | @GET("astros.json") 40 | Call getAstronautsInSpace(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/kousenit/astro/AstroResponse.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.astro; 2 | 3 | import java.util.List; 4 | 5 | public class AstroResponse { 6 | 7 | private final int number; 8 | private final String message; 9 | private final List people; 10 | 11 | public AstroResponse(int number, 12 | String message, 13 | List people) { 14 | this.number = number; 15 | this.message = message; 16 | this.people = people; 17 | } 18 | 19 | public int getNumber() { 20 | return number; 21 | } 22 | 23 | public String getMessage() { 24 | return message; 25 | } 26 | 27 | public List getPeople() { 28 | return people; 29 | } 30 | 31 | @Override 32 | public String toString() { 33 | return "AstroResponse{" + 34 | "number=" + number + 35 | ", message='" + message + '\'' + 36 | ", people=" + people + 37 | '}'; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/kousenit/astro/AstroService.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.astro; 2 | 3 | import java.util.Map; 4 | import java.util.stream.Collectors; 5 | 6 | public class AstroService { 7 | private final Gateway gateway; 8 | 9 | public AstroService(Gateway gateway) { 10 | this.gateway = gateway; 11 | } 12 | 13 | public Map getAstroData() { 14 | AstroResponse response = gateway.getResponse(); 15 | return groupByCraft(response); 16 | } 17 | 18 | private Map groupByCraft(AstroResponse data) { 19 | return data.getPeople().stream() 20 | .collect(Collectors.groupingBy( 21 | Assignment::getCraft, Collectors.counting())); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/kousenit/astro/Gateway.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.astro; 2 | 3 | public interface Gateway { 4 | T getResponse(); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/com/kousenit/astro/astro_data.json: -------------------------------------------------------------------------------- 1 | { 2 | "message": "success", 3 | "people": [ 4 | { 5 | "name": "Mark Vande Hei", 6 | "craft": "ISS" 7 | }, 8 | { 9 | "name": "Pyotr Dubrov", 10 | "craft": "ISS" 11 | }, 12 | { 13 | "name": "Anton Shkaplerov", 14 | "craft": "ISS" 15 | }, 16 | { 17 | "name": "Zhai Zhigang", 18 | "craft": "Shenzhou 13" 19 | }, 20 | { 21 | "name": "Wang Yaping", 22 | "craft": "Shenzhou 13" 23 | }, 24 | { 25 | "name": "Ye Guangfu", 26 | "craft": "Shenzhou 13" 27 | }, 28 | { 29 | "name": "Raja Chari", 30 | "craft": "ISS" 31 | }, 32 | { 33 | "name": "Tom Marshburn", 34 | "craft": "ISS" 35 | }, 36 | { 37 | "name": "Kayla Barron", 38 | "craft": "ISS" 39 | }, 40 | { 41 | "name": "Matthias Maurer", 42 | "craft": "ISS" 43 | } 44 | ], 45 | "number": 10 46 | } -------------------------------------------------------------------------------- /src/main/java/com/kousenit/hr/DefaultTranslationService.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.hr; 2 | 3 | public class DefaultTranslationService implements TranslationService { 4 | public String translate(String text, String sourceLanguage, String targetLanguage) { 5 | return TranslationService.super.translate(text, "en", "en"); 6 | } 7 | 8 | public String translate(String text) { 9 | return text; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/kousenit/hr/HelloMockito.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.hr; 2 | 3 | import java.util.Optional; 4 | 5 | public class HelloMockito { 6 | private String greeting = "Hello, %s, from Mockito!"; 7 | private final PersonRepository personRepository; 8 | private final TranslationService translationService; 9 | 10 | public HelloMockito(PersonRepository personRepository, TranslationService translationService) { 11 | this.personRepository = personRepository; 12 | this.translationService = translationService; 13 | } 14 | 15 | public String greet(int id, String sourceLanguage, String targetLanguage) { 16 | Optional person = personRepository.findById(id); 17 | String name = person.map(Person::getFirst).orElse("World"); 18 | return translationService.translate( 19 | String.format(greeting, name), sourceLanguage, targetLanguage); 20 | } 21 | 22 | 23 | public HelloMockito(PersonRepository personRepository) { 24 | this(personRepository, new DefaultTranslationService()); 25 | } 26 | 27 | public HelloMockito(TranslationService service) { 28 | this(new InMemoryPersonRepository(), service); 29 | } 30 | 31 | @SuppressWarnings("unused") 32 | public String greet(int id) { 33 | Optional person = personRepository.findById(id); 34 | String name = person.map(Person::getFirst).orElse("World"); 35 | return translationService.translate(String.format(greeting, name)); 36 | } 37 | 38 | public String greet(Person person, String sourceLanguage, String targetLanguage) { 39 | return translationService.translate( 40 | String.format(greeting, person.getFirst()), sourceLanguage, targetLanguage); 41 | } 42 | 43 | public void setGreeting(String greeting) { 44 | this.greeting = greeting; 45 | } 46 | 47 | public String getGreeting() { 48 | return greeting; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/kousenit/hr/InMemoryPersonRepository.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.hr; 2 | 3 | import java.util.*; 4 | import java.util.stream.Collectors; 5 | 6 | public class InMemoryPersonRepository implements PersonRepository { 7 | private final List people = new ArrayList<>(); 8 | 9 | @Override 10 | public final Person save(Person person) { 11 | synchronized (people) { 12 | people.add(person); 13 | } 14 | return person; 15 | } 16 | 17 | @Override 18 | public Optional findById(int id) { 19 | Map peopleMap = 20 | people.stream().collect(Collectors.toMap(Person::getId, p -> p)); 21 | return Optional.ofNullable(peopleMap.get(id)); 22 | } 23 | 24 | @Override 25 | public List findAll() { 26 | return people; 27 | } 28 | 29 | @Override 30 | public long count() { 31 | return people.size(); 32 | } 33 | 34 | @Override 35 | public final void delete(Person person) { 36 | synchronized (people) { 37 | people.remove(person); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/kousenit/hr/Person.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.hr; 2 | 3 | import java.time.LocalDate; 4 | import java.util.Objects; 5 | 6 | public class Person { 7 | private final Integer id; 8 | private final String first; 9 | private final String last; 10 | private final LocalDate dob; 11 | 12 | Person(Integer id, String first, String last, LocalDate dob) { 13 | this.id = id; 14 | this.first = first; 15 | this.last = last; 16 | this.dob = dob; 17 | } 18 | 19 | public Integer getId() { 20 | return id; 21 | } 22 | 23 | public String getFirst() { 24 | return first; 25 | } 26 | 27 | public String getLast() { 28 | return last; 29 | } 30 | 31 | public LocalDate getDob() { 32 | return dob; 33 | } 34 | 35 | @Override 36 | public boolean equals(Object obj) { 37 | if (obj == this) return true; 38 | if (obj == null || obj.getClass() != this.getClass()) return false; 39 | Person that = (Person) obj; 40 | return Objects.equals(this.id, that.id) && 41 | Objects.equals(this.first, that.first) && 42 | Objects.equals(this.last, that.last) && 43 | Objects.equals(this.dob, that.dob); 44 | } 45 | 46 | @Override 47 | public int hashCode() { 48 | return Objects.hash(id, first, last, dob); 49 | } 50 | 51 | @Override 52 | public String toString() { 53 | return "Person[" + 54 | "id=" + id + ", " + 55 | "first=" + first + ", " + 56 | "last=" + last + ", " + 57 | "dob=" + dob + ']'; 58 | } 59 | } -------------------------------------------------------------------------------- /src/main/java/com/kousenit/hr/PersonRepository.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.hr; 2 | 3 | import java.util.List; 4 | import java.util.Optional; 5 | 6 | public interface PersonRepository { 7 | Person save(Person person); 8 | Optional findById(int id); 9 | List findAll(); 10 | long count(); 11 | void delete(Person person); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/kousenit/hr/PersonService.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.hr; 2 | 3 | import java.time.LocalDate; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | import java.util.Optional; 7 | import java.util.concurrent.CompletableFuture; 8 | import java.util.stream.Collectors; 9 | 10 | public class PersonService { 11 | private final PersonRepository repository; 12 | 13 | public PersonService(PersonRepository repository) { 14 | this.repository = repository; 15 | } 16 | 17 | public List savePeople(Person... person) { 18 | return Arrays.stream(person) 19 | .map(repository::save) 20 | .map(Person::getId) 21 | .collect(Collectors.toList()); 22 | } 23 | 24 | public void asyncSavePerson(Person person, long delay) { 25 | CompletableFuture.runAsync(() -> { 26 | System.out.println("Running on thread " + Thread.currentThread().getName()); 27 | try { 28 | Thread.sleep(delay); 29 | } catch (InterruptedException ignored) { 30 | } 31 | repository.save(person); 32 | }); 33 | } 34 | 35 | public List getLastNames() { 36 | return repository.findAll().stream() 37 | .map(Person::getLast) 38 | .collect(Collectors.toList()); 39 | } 40 | 41 | public List findByIds(int... ids) { 42 | return Arrays.stream(ids) 43 | .mapToObj(repository::findById) 44 | .filter(Optional::isPresent) 45 | .map(Optional::get) 46 | .collect(Collectors.toList()); 47 | } 48 | 49 | public Integer getHighestId() { 50 | return repository.findAll().stream() 51 | .map(Person::getId) 52 | .max(Integer::compareTo).orElse(0); 53 | } 54 | 55 | public Person createPerson(int id, String first, String last, LocalDate dob) { 56 | Person person = new Person(id, first, last, dob); 57 | return repository.save(person); 58 | } 59 | 60 | public Person createPerson(int id, String first, String last, String dobString) { 61 | Person person = new Person(id, first, last, LocalDate.parse(dobString)); 62 | return repository.save(person); 63 | } 64 | 65 | public long getTotalPeople() { 66 | return repository.count(); 67 | } 68 | 69 | public void deleteAll() { 70 | repository.findAll() 71 | .forEach(repository::delete); 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/com/kousenit/hr/TranslationService.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.hr; 2 | 3 | public interface TranslationService { 4 | 5 | default String translate(String text, String sourceLanguage, String targetLanguage) { 6 | return text; 7 | } 8 | 9 | String translate(String text); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/kousenit/inorder/OrderService.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.inorder; 2 | 3 | public class OrderService { 4 | // Dependencies of the class under test 5 | private final PaymentService paymentService; 6 | private final ShippingService shippingService; 7 | 8 | // Constructor to make it easy to inject the dependencies 9 | public OrderService(PaymentService paymentService, ShippingService shippingService) { 10 | this.paymentService = paymentService; 11 | this.shippingService = shippingService; 12 | } 13 | 14 | // Need to test this method 15 | public boolean processOrder(double amount, String address) { 16 | if (paymentService.processPayment(amount)) { 17 | return shippingService.shipProduct(address); 18 | } else { 19 | return false; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/kousenit/inorder/PaymentProcessor.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.inorder; 2 | 3 | public interface PaymentProcessor { 4 | boolean authorizePayment(double amount); 5 | boolean capturePayment(); // request the authorized funds 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/kousenit/inorder/PaymentService.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.inorder; 2 | 3 | public class PaymentService { 4 | // Dependency of the class under test 5 | private final PaymentProcessor processor; 6 | 7 | // Constructor to make it easy to inject the dependency 8 | public PaymentService(PaymentProcessor processor) { 9 | this.processor = processor; 10 | } 11 | 12 | // Need to test this method 13 | public boolean processPayment(double amount) { 14 | // Payment must be authorized before collecting the money 15 | if (processor.authorizePayment(amount)) { 16 | return processor.capturePayment(); 17 | } 18 | return false; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/kousenit/inorder/ShippingService.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.inorder; 2 | 3 | public interface ShippingService { 4 | boolean shipProduct(String address); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/com/kousenit/pubsub/Publisher.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.pubsub; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | // Adapted from a similar example in the Spock framework 7 | public class Publisher { 8 | private final List subscribers = new ArrayList<>(); 9 | 10 | public void subscribe(Subscriber sub) { 11 | subscribers.add(sub); 12 | } 13 | 14 | // Want to test this method 15 | public void send(String message) { 16 | for (Subscriber sub : subscribers) { 17 | try { 18 | sub.onNext(message); 19 | } catch (Exception ignored) { 20 | // evil, but what can you do? 21 | } 22 | } 23 | } 24 | 25 | public void sendParallel(String message) { 26 | subscribers.parallelStream().forEach(sub -> { 27 | try { 28 | sub.onNext(message); 29 | } catch (Exception ignored) { 30 | } 31 | }); 32 | } 33 | 34 | } 35 | 36 | -------------------------------------------------------------------------------- /src/main/java/com/kousenit/pubsub/Subscriber.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.pubsub; 2 | 3 | public interface Subscriber { 4 | void onNext(String message); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/com/kousenit/simple/AddingMachine.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.simple; 2 | 3 | import java.util.List; 4 | 5 | public class AddingMachine { 6 | private final List numbers; 7 | 8 | public AddingMachine(List numbers) { 9 | this.numbers = numbers; 10 | } 11 | 12 | @SuppressWarnings("ForLoopReplaceableByForEach") 13 | public int getTotalUsingLoop() { 14 | int total = 0; 15 | int count = numbers.size(); 16 | for (int i = 0; i < count; i++) { 17 | total += numbers.get(i); 18 | } 19 | return total; 20 | } 21 | 22 | public int getTotalUsingIterable() { 23 | int total = 0; 24 | for (int n : numbers) { 25 | total += n; 26 | } 27 | return total; 28 | } 29 | 30 | public int getTotalUsingStream() { 31 | return numbers.stream() 32 | .mapToInt(Integer::valueOf) 33 | .sum(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/kousenit/simple/HelloService.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.simple; 2 | 3 | public class HelloService { 4 | 5 | private final TranslateService translateService; 6 | 7 | public HelloService(TranslateService translateService) { 8 | this.translateService = translateService; 9 | } 10 | 11 | public String greet(String name, String language) { 12 | String greeting = "Hello, " + name + "!"; 13 | return translateService.translate(greeting, language); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/kousenit/simple/LoggingDemo.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.simple; 2 | 3 | import java.util.logging.Logger; 4 | 5 | public class LoggingDemo { 6 | private final Logger logger; 7 | 8 | public LoggingDemo(Logger logger) { 9 | this.logger = logger; 10 | } 11 | 12 | public void doStuff(String message) { 13 | System.out.printf("Doing useful stuff: %s%n", message); 14 | logger.info(message); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/kousenit/simple/TranslateService.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.simple; 2 | 3 | public class TranslateService { 4 | 5 | // Translate from English to whatever locale is specified 6 | public String translate(String text, String language) { 7 | return language + " translation: " + text; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/kousenit/wikipedia/BioService.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.wikipedia; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | import java.util.stream.Collectors; 6 | 7 | public class BioService { 8 | private final List pageNames; 9 | 10 | public BioService(String... pageNames) { 11 | this.pageNames = Arrays.stream(pageNames) 12 | .collect(Collectors.toList()); 13 | } 14 | 15 | public List getBios() { 16 | return pageNames.stream() 17 | .map(WikiUtil::getWikipediaExtract) 18 | .collect(Collectors.toList()); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/kousenit/wikipedia/WikiPage.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.wikipedia; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | 5 | @SuppressWarnings("unused") 6 | public class WikiPage { 7 | @JsonIgnore 8 | private int pageid; 9 | @JsonIgnore 10 | private int ns; 11 | private String title; 12 | private String extract; 13 | @JsonIgnore 14 | private boolean missing; 15 | 16 | public int getPageid() { 17 | return pageid; 18 | } 19 | 20 | public void setPageid(int pageid) { 21 | this.pageid = pageid; 22 | } 23 | 24 | public int getNs() { 25 | return ns; 26 | } 27 | 28 | public void setNs(int ns) { 29 | this.ns = ns; 30 | } 31 | 32 | public String getTitle() { 33 | return title; 34 | } 35 | 36 | public void setTitle(String title) { 37 | this.title = title; 38 | } 39 | 40 | public String getExtract() { 41 | return extract; 42 | } 43 | 44 | public void setExtract(String extract) { 45 | this.extract = extract; 46 | } 47 | 48 | public boolean getMissing() { 49 | return missing; 50 | } 51 | 52 | public void setMissing(boolean missing) { 53 | this.missing = missing; 54 | } 55 | 56 | @Override 57 | public String toString() { 58 | return "WikiPage{" + 59 | "pageid=" + pageid + 60 | ", ns=" + ns + 61 | ", title='" + title + '\'' + 62 | ", extract='" + extract + '\'' + 63 | ", missing=" + missing + 64 | '}'; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/kousenit/wikipedia/WikiQuery.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.wikipedia; 2 | 3 | import java.util.List; 4 | 5 | @SuppressWarnings("unused") 6 | public class WikiQuery { 7 | private List pages; 8 | 9 | public List getPages() { 10 | return pages; 11 | } 12 | 13 | public void setPages(List pages) { 14 | this.pages = pages; 15 | } 16 | 17 | @Override 18 | public String toString() { 19 | return "WikiQuery{" + 20 | "pages=" + pages + 21 | '}'; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/kousenit/wikipedia/WikiResponse.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.wikipedia; 2 | 3 | @SuppressWarnings("unused") 4 | public class WikiResponse { 5 | private String batchcomplete; 6 | private WikiQuery query; 7 | 8 | public String getBatchcomplete() { 9 | return batchcomplete; 10 | } 11 | 12 | public void setBatchcomplete(String batchcomplete) { 13 | this.batchcomplete = batchcomplete; 14 | } 15 | 16 | public WikiQuery getQuery() { 17 | return query; 18 | } 19 | 20 | public void setQuery(WikiQuery query) { 21 | this.query = query; 22 | } 23 | 24 | @Override 25 | public String toString() { 26 | return "WikiResponse{" + 27 | "batchcomplete=" + batchcomplete + 28 | ", query=" + query + 29 | '}'; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/kousenit/wikipedia/WikiUtil.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.wikipedia; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | 6 | import java.io.IOException; 7 | import java.net.URI; 8 | import java.net.URLEncoder; 9 | import java.net.http.HttpClient; 10 | import java.net.http.HttpRequest; 11 | import java.net.http.HttpResponse; 12 | import java.nio.charset.StandardCharsets; 13 | import java.time.Duration; 14 | import java.util.Map; 15 | import java.util.stream.Collectors; 16 | 17 | public class WikiUtil { 18 | public static String getWikipediaExtract(String title) { 19 | String base = "https://en.wikipedia.org/w/api.php"; 20 | Map params = Map.ofEntries( 21 | Map.entry("action", "query"), 22 | Map.entry("prop", "extracts"), 23 | Map.entry("format", "json"), 24 | Map.entry("exintro", "true"), 25 | Map.entry("explaintext", "1"), 26 | Map.entry("titles", URLEncoder.encode(title, StandardCharsets.UTF_8)), 27 | Map.entry("redirects", "1"), 28 | Map.entry("formatversion", "2") 29 | ); 30 | String queryString = params.entrySet().stream() 31 | .map(Map.Entry::toString) 32 | .collect(Collectors.joining("&")); 33 | try { 34 | return getResponse(String.format("%s?%s", base, queryString)); 35 | } catch (IOException | InterruptedException e) { 36 | throw new RuntimeException(e); 37 | } 38 | } 39 | 40 | private static String getResponse(String url) throws IOException, InterruptedException { 41 | HttpClient client = HttpClient.newHttpClient(); 42 | HttpRequest request = HttpRequest.newBuilder() 43 | .uri(URI.create(url)) 44 | .timeout(Duration.ofSeconds(2)) 45 | .build(); 46 | return parseResponse(client.send(request, HttpResponse.BodyHandlers.ofString())); 47 | } 48 | 49 | private static String parseResponse(HttpResponse response) throws JsonProcessingException { 50 | ObjectMapper mapper = new ObjectMapper(); 51 | WikiResponse json = mapper.readValue(response.body(), WikiResponse.class); 52 | WikiPage page = json.getQuery().getPages().get(0); 53 | if (page.getExtract() == null) { 54 | throw new RuntimeException("Page not found"); 55 | } 56 | return Map.of(page.getTitle(), page.getExtract()).toString(); 57 | } 58 | } -------------------------------------------------------------------------------- /src/test/java/com/kousenit/astro/AssignmentTest.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.astro; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.*; 6 | 7 | class AssignmentTest { 8 | 9 | @Test 10 | void testConstructorAndGetters() { 11 | // Arrange 12 | String name = "Ellen Ripley"; 13 | String craft = "Nostromo"; 14 | 15 | // Act 16 | Assignment assignment = new Assignment(name, craft); 17 | 18 | // Assert 19 | assertEquals(name, assignment.getName()); 20 | assertEquals(craft, assignment.getCraft()); 21 | } 22 | 23 | @Test 24 | void testToString() { 25 | // Arrange 26 | String name = "Ellen Ripley"; 27 | String craft = "Nostromo"; 28 | Assignment assignment = new Assignment(name, craft); 29 | 30 | // Act 31 | String result = assignment.toString(); 32 | 33 | // Assert 34 | assertTrue(result.contains(name)); 35 | assertTrue(result.contains(craft)); 36 | } 37 | } -------------------------------------------------------------------------------- /src/test/java/com/kousenit/astro/AstroGatewayHttpClientTest.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.astro; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.*; 6 | 7 | class AstroGatewayHttpClientTest { 8 | private final Gateway gateway = new AstroGatewayHttpClient(); 9 | 10 | @Test 11 | void testDeserializeToRecords() { 12 | AstroResponse result = gateway.getResponse(); 13 | result.getPeople().forEach(System.out::println); 14 | assertAll( 15 | () -> assertTrue(result.getNumber() >= 0), 16 | () -> assertEquals(result.getPeople().size(), result.getNumber()) 17 | ); 18 | } 19 | } -------------------------------------------------------------------------------- /src/test/java/com/kousenit/astro/AstroGatewayRetrofitTest.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.astro; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.*; 6 | 7 | class AstroGatewayRetrofitTest { 8 | private final Gateway gateway = new AstroGatewayRetrofit(); 9 | 10 | @Test 11 | void testDeserializeToRecords() { 12 | AstroResponse result = gateway.getResponse(); 13 | result.getPeople().forEach(System.out::println); 14 | assertAll( 15 | () -> assertTrue(result.getNumber() >= 0), 16 | () -> assertEquals(result.getPeople().size(), result.getNumber()) 17 | ); 18 | } 19 | } -------------------------------------------------------------------------------- /src/test/java/com/kousenit/astro/AstroServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.astro; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.junit.jupiter.api.extension.ExtendWith; 5 | import org.mockito.InjectMocks; 6 | import org.mockito.Mock; 7 | import org.mockito.junit.jupiter.MockitoExtension; 8 | 9 | import java.io.IOException; 10 | import java.util.Arrays; 11 | import java.util.Map; 12 | 13 | import static org.assertj.core.api.Assertions.assertThat; 14 | import static org.assertj.core.api.Assertions.assertThatExceptionOfType; 15 | import static org.junit.jupiter.api.Assertions.*; 16 | import static org.mockito.BDDMockito.*; 17 | 18 | @ExtendWith(MockitoExtension.class) 19 | class AstroServiceTest { 20 | private final AstroResponse mockAstroResponse = 21 | new AstroResponse(7, "Success", Arrays.asList( 22 | new Assignment("John Sheridan", "Babylon 5"), 23 | new Assignment("Susan Ivanova", "Babylon 5"), 24 | new Assignment("Beckett Mariner", "USS Cerritos"), 25 | new Assignment("Brad Boimler", "USS Cerritos"), 26 | new Assignment("Sam Rutherford", "USS Cerritos"), 27 | new Assignment("D'Vana Tendi", "USS Cerritos"), 28 | new Assignment("Ellen Ripley", "Nostromo") 29 | )); 30 | 31 | @Mock 32 | private Gateway gateway; 33 | 34 | @InjectMocks 35 | private AstroService service; 36 | 37 | @Test 38 | void testAstroData_usingInjectedMockGateway() { 39 | // Mock Gateway created and injected into AstroService using 40 | // @Mock and @InjectMock annotations 41 | // 42 | // Set the expectations on the mock 43 | when(gateway.getResponse()) 44 | .thenReturn(mockAstroResponse); 45 | 46 | // Call the method under test 47 | Map astroData = service.getAstroData(); 48 | 49 | // Check the results from the method under test (AssertJ) 50 | assertThat(astroData) 51 | .containsEntry("Babylon 5", 2L) 52 | .containsEntry("Nostromo", 1L) 53 | .containsEntry("USS Cerritos", 4L); 54 | 55 | // Verify the stubbed method was called 56 | verify(gateway).getResponse(); 57 | // verify(gateway, times(1)).getResponse(); 58 | } 59 | 60 | // Unit test with injected mock Gateway (uses annotations) 61 | @Test 62 | void testAstroData_usingInjectedMockGatewayBDD() { 63 | // Mock Gateway created and injected into AstroService using 64 | // @Mock and @InjectMock annotations 65 | // 66 | // Set the expectations on the mock 67 | given(gateway.getResponse()) 68 | .willReturn(mockAstroResponse); 69 | 70 | // Call the method under test 71 | Map astroData = service.getAstroData(); 72 | 73 | // Check the results from the method under test (AssertJ) 74 | assertThat(astroData) 75 | .containsEntry("Babylon 5", 2L) 76 | .containsEntry("Nostromo", 1L) 77 | .containsEntry("USS Cerritos", 4L); 78 | 79 | // Verify the stubbed method was called 80 | then(gateway).should().getResponse(); 81 | } 82 | 83 | // Check network failure 84 | @Test 85 | void testAstroData_usingFailedGateway() { 86 | when(gateway.getResponse()).thenThrow( 87 | new RuntimeException(new IOException("Network problems"))); 88 | 89 | // Check the exception (JUnit 5, which isn't bad) 90 | RuntimeException exception = 91 | assertThrows(RuntimeException.class, () -> service.getAstroData()); 92 | Throwable cause = exception.getCause(); 93 | assertAll( 94 | () -> assertEquals(IOException.class, cause.getClass()), 95 | () -> assertEquals("Network problems", cause.getMessage()) 96 | ); 97 | 98 | // Check the exception (AssertJ, which is way, way better :) 99 | assertThatExceptionOfType(RuntimeException.class) 100 | .isThrownBy(() -> service.getAstroData()) 101 | .withCauseExactlyInstanceOf(IOException.class) 102 | .withMessageContaining("Network problems"); 103 | } 104 | 105 | // Check network failure 106 | @Test 107 | void testAstroData_usingFailedGatewayBDD() { 108 | // given: 109 | given(gateway.getResponse()).willThrow( 110 | new RuntimeException(new IOException("Network problems"))); 111 | 112 | // when: 113 | Exception exception = assertThrows(RuntimeException.class, 114 | () -> service.getAstroData()); 115 | 116 | // then: 117 | Throwable cause = exception.getCause(); 118 | assertAll( 119 | () -> assertEquals(IOException.class, cause.getClass()), 120 | () -> assertEquals("Network problems", cause.getMessage()) 121 | ); 122 | 123 | then(gateway).should().getResponse(); 124 | } 125 | 126 | // Integration test -- no mocks 127 | @Test 128 | void testAstroData_usingRealGateway_withRetrofit() { 129 | // Create an instance of AstroService using the real Gateway 130 | service = new AstroService(new AstroGatewayRetrofit()); 131 | 132 | // Call the method under test 133 | Map astroData = service.getAstroData(); 134 | 135 | // Print the results and check that they are reasonable 136 | astroData.forEach((craft, number) -> { 137 | System.out.println(number + " astronauts aboard " + craft); 138 | assertAll( 139 | () -> assertThat(number).isPositive(), 140 | () -> assertThat(craft).isNotBlank() 141 | ); 142 | }); 143 | } 144 | 145 | // Integration test -- no mocks 146 | @Test 147 | @org.junit.jupiter.api.Disabled("API connection timeout - disabled to allow tests to pass") 148 | void testAstroData_usingRealGateway_withHttpClient() { 149 | // Create an instance of AstroService using the real Gateway 150 | service = new AstroService(new AstroGatewayHttpClient()); 151 | 152 | // Call the method under test 153 | Map astroData = service.getAstroData(); 154 | 155 | // Print the results and check that they are reasonable 156 | astroData.forEach((craft, number) -> { 157 | System.out.println(number + " astronauts aboard " + craft); 158 | assertAll( 159 | () -> assertThat(number).isPositive(), 160 | () -> assertThat(craft).isNotBlank() 161 | ); 162 | }); 163 | } 164 | 165 | // Own mock class -- FakeGateway 166 | @Test 167 | void testAstroData_usingOwnMockGateway() { 168 | // Create the service using the mock Gateway 169 | service = new AstroService(new FakeGateway()); 170 | 171 | // Call the method under test 172 | Map astroData = service.getAstroData(); 173 | 174 | // Check the results from the method under test 175 | assertThat(astroData) 176 | .containsOnlyKeys("USS Voyager", "Jupiter 2", 177 | "Babylon 5", "Rocinante", "Nostromo") 178 | .containsEntry("USS Voyager", 2L) 179 | .containsEntry("Jupiter 2", 1L) 180 | .containsEntry("Babylon 5", 1L) 181 | .containsEntry("Rocinante", 2L) 182 | .containsEntry("Nostromo", 1L); 183 | } 184 | 185 | // Unit test with mock Gateway using mock(Gateway.class) 186 | @SuppressWarnings("unchecked") 187 | @Test 188 | void testAstroData_usingMockGateway() { 189 | // 1. Create a mock Gateway 190 | Gateway mockGateway = mock(Gateway.class); 191 | 192 | // 2. Set expectations on the mock Gateway 193 | when(mockGateway.getResponse()) 194 | .thenReturn(mockAstroResponse); 195 | 196 | // 3. Create an instance of AstroService using the mock Gateway 197 | AstroService service = new AstroService(mockGateway); 198 | 199 | // 4. Call the method under test 200 | Map astroData = service.getAstroData(); 201 | 202 | // 5. Check the results from the method under test 203 | assertThat(astroData) 204 | .containsEntry("Babylon 5", 2L) 205 | .containsEntry("USS Cerritos", 4L) 206 | .containsEntry("Nostromo", 1L) 207 | .hasSize(3); 208 | 209 | // 6. Verify that the mock Gateway method was called 210 | verify(mockGateway).getResponse(); 211 | } 212 | 213 | } -------------------------------------------------------------------------------- /src/test/java/com/kousenit/astro/FakeGateway.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.astro; 2 | 3 | import java.util.List; 4 | 5 | public class FakeGateway implements Gateway { 6 | @Override 7 | public AstroResponse getResponse() { 8 | return new AstroResponse(7, "Success", 9 | List.of(new Assignment("Kathryn Janeway", "USS Voyager"), 10 | new Assignment("Seven of Nine", "USS Voyager"), 11 | new Assignment("Will Robinson", "Jupiter 2"), 12 | new Assignment("Lennier", "Babylon 5"), 13 | new Assignment("James Holden", "Rocinante"), 14 | new Assignment("Naomi Negata", "Rocinante"), 15 | new Assignment("Ellen Ripley", "Nostromo"))); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/com/kousenit/hr/DefaultTranslationServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.hr; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.junit.jupiter.api.extension.ExtendWith; 5 | import org.mockito.InjectMocks; 6 | import org.mockito.junit.jupiter.MockitoExtension; 7 | 8 | import static org.junit.jupiter.api.Assertions.assertEquals; 9 | 10 | @ExtendWith(MockitoExtension.class) 11 | class DefaultTranslationServiceTest { 12 | 13 | @InjectMocks 14 | private DefaultTranslationService service; 15 | 16 | @Test 17 | void testTranslateWithoutParameters() { 18 | // Arrange 19 | String text = "Hello, World!"; 20 | 21 | // Act 22 | String result = service.translate(text); 23 | 24 | // Assert 25 | assertEquals(text, result, "Should return the original text unchanged"); 26 | } 27 | 28 | @Test 29 | void testTranslateWithParameters() { 30 | // Arrange 31 | String text = "Hello, World!"; 32 | String sourceLanguage = "en"; 33 | String targetLanguage = "en"; 34 | 35 | // Act 36 | String result = service.translate(text, sourceLanguage, targetLanguage); 37 | 38 | // Assert 39 | assertEquals(text, result, "Should return the original text unchanged"); 40 | } 41 | 42 | @Test 43 | void testTranslateWithDifferentLanguages() { 44 | // Arrange 45 | String text = "Hello, World!"; 46 | String sourceLanguage = "en"; 47 | String targetLanguage = "fr"; 48 | 49 | // Act 50 | String result = service.translate(text, sourceLanguage, targetLanguage); 51 | 52 | // Assert 53 | // The implementation always passes "en" to both source and target 54 | assertEquals(text, result, "Should return the original text unchanged regardless of requested languages"); 55 | } 56 | } -------------------------------------------------------------------------------- /src/test/java/com/kousenit/hr/HelloMockitoTest.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.hr; 2 | 3 | import org.junit.jupiter.api.DisplayName; 4 | import org.junit.jupiter.api.Test; 5 | import org.junit.jupiter.api.extension.ExtendWith; 6 | import org.mockito.InOrder; 7 | import org.mockito.InjectMocks; 8 | import org.mockito.Mock; 9 | import org.mockito.MockedConstruction; 10 | import org.mockito.junit.jupiter.MockitoExtension; 11 | 12 | import java.time.LocalDate; 13 | import java.util.Optional; 14 | 15 | import static org.assertj.core.api.Assertions.assertThat; 16 | import static org.junit.jupiter.api.Assertions.assertEquals; 17 | import static org.mockito.Mockito.*; 18 | import static org.mockito.AdditionalAnswers.returnsFirstArg; 19 | 20 | @ExtendWith(MockitoExtension.class) 21 | class HelloMockitoTest { 22 | @Mock 23 | private PersonRepository repository; 24 | 25 | @Mock 26 | private TranslationService translationService; 27 | 28 | @InjectMocks 29 | private HelloMockito helloMockito; 30 | 31 | @Test 32 | @DisplayName("Greet Admiral Hopper") 33 | void greetForPersonThatExists() { 34 | // Set the expectations on the dependencies 35 | when(repository.findById(anyInt())) 36 | .thenReturn(Optional.of(new Person(1, "Grace", "Hopper", LocalDate.now()))); 37 | when(translationService.translate( 38 | "Hello, Grace, from Mockito!", "en", "en")) 39 | .thenReturn("Hello, Grace, from Mockito!"); 40 | 41 | // Test the greet method 42 | String greeting = helloMockito.greet(1, "en", "en"); 43 | assertEquals("Hello, Grace, from Mockito!", greeting); 44 | 45 | // Verify that the dependencies were called as expected 46 | InOrder inOrder = inOrder(repository, translationService); 47 | inOrder.verify(repository) 48 | .findById(anyInt()); 49 | inOrder.verify(translationService) 50 | .translate(anyString(), eq("en"), eq("en")); 51 | } 52 | 53 | @Test 54 | @DisplayName("Greet a person not in the database") 55 | void greetForPersonThatDoesNotExist() { 56 | when(repository.findById(anyInt())) 57 | .thenReturn(Optional.empty()); 58 | when(translationService.translate( 59 | "Hello, World, from Mockito!", "en", "en")) 60 | .thenReturn("Hello, World, from Mockito!"); 61 | 62 | String greeting = helloMockito.greet(100, "en", "en"); 63 | assertEquals("Hello, World, from Mockito!", greeting); 64 | 65 | InOrder inOrder = inOrder(repository, translationService); 66 | inOrder.verify(repository) 67 | .findById(anyInt()); 68 | inOrder.verify(translationService) 69 | .translate(anyString(), eq("en"), eq("en")); 70 | } 71 | 72 | @Test 73 | void greetWithDefaultTranslator() { 74 | PersonRepository mockRepo = mock(PersonRepository.class); 75 | when(mockRepo.findById(anyInt())) 76 | .thenReturn(Optional.of(new Person(1, "Grace", "Hopper", LocalDate.now()))); 77 | HelloMockito helloMockito = new HelloMockito(mockRepo); 78 | String greeting = helloMockito.greet(1, "en", "en"); 79 | assertThat(greeting).isEqualTo("Hello, Grace, from Mockito!"); 80 | } 81 | 82 | @Test 83 | void greetWithMockedConstructor() { 84 | // Mock for repo (needed for HelloMockito constructor) 85 | PersonRepository mockRepo = mock(PersonRepository.class); 86 | when(mockRepo.findById(anyInt())) 87 | .thenReturn(Optional.of(new Person(1, "Grace", "Hopper", LocalDate.now()))); 88 | 89 | // Mock for translator (instantiated inside HelloMockito constructor) 90 | try (MockedConstruction ignored = 91 | mockConstruction(DefaultTranslationService.class, 92 | (mock, context) -> when(mock.translate(anyString(), anyString(), anyString())) 93 | .thenAnswer(invocation -> invocation.getArgument(0) + " (translated)"))) { 94 | 95 | // Instantiate HelloMockito with mocked repo and locally instantiated translator 96 | HelloMockito hello = new HelloMockito(mockRepo); 97 | String greeting = hello.greet(1, "en", "en"); 98 | assertThat(greeting).isEqualTo("Hello, Grace, from Mockito! (translated)"); 99 | 100 | // Any instantiation of DefaultTranslationService will return the mocked instance 101 | DefaultTranslationService translator = new DefaultTranslationService(); 102 | String translate = translator.translate("What up?", "en", "en"); 103 | assertThat(translate).isEqualTo("What up? (translated)"); 104 | } 105 | } 106 | 107 | @Test 108 | void greetWithMockedConstructorWithAnswer() { 109 | // Mock for repo (needed for HelloMockito constructor) 110 | PersonRepository mockRepo = mock(PersonRepository.class); 111 | when(mockRepo.findById(anyInt())) 112 | .thenReturn(Optional.of(new Person(1, "Grace", "Hopper", LocalDate.now()))); 113 | 114 | // Mock for translator (instantiated inside HelloMockito constructor) 115 | try (MockedConstruction ignored = 116 | mockConstructionWithAnswer(DefaultTranslationService.class, 117 | invocation -> invocation.getArgument(0) + " (translated)", 118 | invocation -> invocation.getArgument(0) + " (translated again)")) { 119 | 120 | // Instantiate HelloMockito with mocked repo and locally instantiated translator 121 | HelloMockito hello = new HelloMockito(mockRepo); 122 | String greeting = hello.greet(1, "en", "en"); 123 | assertThat(greeting).isEqualTo("Hello, Grace, from Mockito! (translated)"); 124 | } 125 | } 126 | 127 | @Test 128 | void testGetterAndSetter() { 129 | assertThat(helloMockito.getGreeting()).isNotNull(); 130 | assertThat(helloMockito.getGreeting()).isEqualTo("Hello, %s, from Mockito!"); 131 | 132 | helloMockito.setGreeting("Hi there, %s, from Mockito!"); 133 | assertThat(helloMockito.getGreeting()).isEqualTo("Hi there, %s, from Mockito!"); 134 | } 135 | 136 | @Test 137 | @DisplayName("Integration test without mocks") 138 | void helloMockitoWithExplicitStubs() { 139 | PersonRepository personRepo = new InMemoryPersonRepository(); 140 | 141 | helloMockito = new HelloMockito( 142 | personRepo, 143 | new DefaultTranslationService() 144 | ); 145 | 146 | // Save a person 147 | Person person = new Person(1, "Grace", "Hopper", LocalDate.now()); 148 | personRepo.save(person); 149 | 150 | // Greet a user that exists 151 | String greeting = helloMockito.greet(1, "en", "en"); 152 | assertThat(greeting).isEqualTo("Hello, Grace, from Mockito!"); 153 | 154 | // Greet a user that does not exist 155 | greeting = helloMockito.greet(100, "en", "en"); 156 | assertThat(greeting).isEqualTo("Hello, World, from Mockito!"); 157 | } 158 | 159 | @Test 160 | @DisplayName("Greet Admiral Hopper") 161 | void greetAPersonUsingAnswers() { 162 | // Set the expectations on the dependencies 163 | when(repository.findById(anyInt())) 164 | .thenReturn(Optional.of(new Person(1, "Grace", "Hopper", LocalDate.now()))); 165 | when(translationService.translate( 166 | anyString(), eq("en"), eq("en"))) 167 | .thenAnswer(returnsFirstArg()); 168 | 169 | // Test the greet method 170 | String greeting = helloMockito.greet(1, "en", "en"); 171 | assertEquals("Hello, Grace, from Mockito!", greeting); 172 | 173 | // Verify that the dependencies were called as expected 174 | verify(repository) 175 | .findById(anyInt()); 176 | verify(translationService) 177 | // gives an error: if one arg is an argument matcher, they all have to be 178 | // .translate(anyString(), "en", "en"); 179 | .translate(anyString(), eq("en"), eq("en")); 180 | } 181 | 182 | @Test 183 | void greetPersonWithSpecifiedLanguages() { 184 | Person hopper = new Person(1, "Grace", "Hopper", 185 | LocalDate.of(1906, 12, 9)); 186 | 187 | TranslationService mockTranslator = mock(TranslationService.class); 188 | when(mockTranslator.translate(anyString(), anyString(), anyString())) 189 | .thenReturn(String.format("Hello, %s, from Mockito", hopper.getFirst()) 190 | + " (translated)"); 191 | 192 | HelloMockito helloMockito = new HelloMockito(mockTranslator); 193 | String greeting = helloMockito.greet(hopper, "en", "en"); 194 | assertThat(greeting).isEqualTo("Hello, Grace, from Mockito (translated)"); 195 | } 196 | } -------------------------------------------------------------------------------- /src/test/java/com/kousenit/hr/InMemoryPersonRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.hr; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.time.LocalDate; 7 | import java.time.Month; 8 | import java.util.List; 9 | import java.util.Optional; 10 | 11 | import static org.junit.jupiter.api.Assertions.*; 12 | 13 | public class InMemoryPersonRepositoryTest { 14 | private InMemoryPersonRepository repository; 15 | private final Person person1 = new Person(1, "Grace", "Hopper", 16 | LocalDate.of(1906, Month.DECEMBER, 9)); 17 | private final Person person2 = new Person(2, "Ada", "Lovelace", 18 | LocalDate.of(1815, Month.DECEMBER, 10)); 19 | private final Person person3 = new Person(3, "Barbara", "Liskov", 20 | LocalDate.of(1939, Month.NOVEMBER, 7)); 21 | 22 | @BeforeEach 23 | void setUp() { 24 | repository = new InMemoryPersonRepository(); 25 | } 26 | 27 | @Test 28 | void testSave() { 29 | Person savedPerson = repository.save(person1); 30 | assertEquals(person1, savedPerson); 31 | assertEquals(1, repository.count()); 32 | } 33 | 34 | @Test 35 | void testFindById_whenExists() { 36 | repository.save(person1); 37 | repository.save(person2); 38 | 39 | Optional foundPerson = repository.findById(1); 40 | assertTrue(foundPerson.isPresent()); 41 | assertEquals(person1, foundPerson.get()); 42 | } 43 | 44 | @Test 45 | void testFindById_whenDoesNotExist() { 46 | repository.save(person1); 47 | 48 | Optional foundPerson = repository.findById(999); 49 | assertFalse(foundPerson.isPresent()); 50 | } 51 | 52 | @Test 53 | void testFindAll() { 54 | repository.save(person1); 55 | repository.save(person2); 56 | repository.save(person3); 57 | 58 | List people = repository.findAll(); 59 | assertEquals(3, people.size()); 60 | assertTrue(people.contains(person1)); 61 | assertTrue(people.contains(person2)); 62 | assertTrue(people.contains(person3)); 63 | } 64 | 65 | @Test 66 | void testCount() { 67 | assertEquals(0, repository.count()); 68 | 69 | repository.save(person1); 70 | assertEquals(1, repository.count()); 71 | 72 | repository.save(person2); 73 | assertEquals(2, repository.count()); 74 | } 75 | 76 | @Test 77 | void testDelete() { 78 | repository.save(person1); 79 | repository.save(person2); 80 | assertEquals(2, repository.count()); 81 | 82 | repository.delete(person1); 83 | assertEquals(1, repository.count()); 84 | assertFalse(repository.findById(person1.getId()).isPresent()); 85 | assertTrue(repository.findById(person2.getId()).isPresent()); 86 | } 87 | 88 | @Test 89 | void testDelete_whenPersonNotInRepository() { 90 | repository.save(person1); 91 | repository.save(person2); 92 | assertEquals(2, repository.count()); 93 | 94 | repository.delete(person3); 95 | assertEquals(2, repository.count()); 96 | } 97 | 98 | @Test 99 | void testThreadSafety() throws InterruptedException { 100 | // This test verifies the synchronized blocks work correctly 101 | final int personCount = 100; 102 | 103 | // Create and start multiple threads that save persons concurrently 104 | Thread thread1 = new Thread(() -> { 105 | for (int i = 0; i < personCount; i++) { 106 | repository.save(new Person(i, "Thread1", "Person" + i, LocalDate.now())); 107 | } 108 | }); 109 | 110 | Thread thread2 = new Thread(() -> { 111 | for (int i = personCount; i < personCount * 2; i++) { 112 | repository.save(new Person(i, "Thread2", "Person" + i, LocalDate.now())); 113 | } 114 | }); 115 | 116 | thread1.start(); 117 | thread2.start(); 118 | 119 | // Wait for both threads to complete 120 | thread1.join(); 121 | thread2.join(); 122 | 123 | // Verify that all persons were saved correctly 124 | assertEquals(personCount * 2, repository.count()); 125 | } 126 | } -------------------------------------------------------------------------------- /src/test/java/com/kousenit/hr/PersonServiceBDDTest.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.hr; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.junit.jupiter.api.extension.ExtendWith; 5 | import org.mockito.InjectMocks; 6 | import org.mockito.Mock; 7 | import org.mockito.junit.jupiter.MockitoExtension; 8 | 9 | import java.time.LocalDate; 10 | import java.time.Month; 11 | import java.time.format.DateTimeParseException; 12 | import java.util.Arrays; 13 | import java.util.List; 14 | 15 | import static org.assertj.core.api.Assertions.assertThat; 16 | import static org.junit.jupiter.api.Assertions.assertEquals; 17 | import static org.junit.jupiter.api.Assertions.assertThrows; 18 | import static org.mockito.BDDMockito.*; 19 | 20 | @ExtendWith(MockitoExtension.class) 21 | public class PersonServiceBDDTest { 22 | @Mock 23 | private PersonRepository repository; 24 | 25 | @InjectMocks 26 | private PersonService service; 27 | 28 | private final List people = Arrays.asList( 29 | new Person(1, "Grace", "Hopper", 30 | LocalDate.of(1906, Month.DECEMBER, 9)), 31 | new Person(2, "Ada", "Lovelace", 32 | LocalDate.of(1815, Month.DECEMBER, 10)), 33 | new Person(3, "Adele", "Goldberg", 34 | LocalDate.of(1945, Month.JULY, 7)), 35 | new Person(14, "Anita", "Borg", 36 | LocalDate.of(1949, Month.JANUARY, 17)), 37 | new Person(5, "Barbara", "Liskov", 38 | LocalDate.of(1939, Month.NOVEMBER, 7))); 39 | 40 | @Test 41 | public void findMaxId() { 42 | given(repository.findAll()).willReturn(people); 43 | assertThat(service.getHighestId()).isEqualTo(14); 44 | } 45 | 46 | @Test 47 | public void getLastNames() { 48 | given(repository.findAll()).willReturn(people); 49 | assertThat(service.getLastNames()) 50 | .containsExactly("Hopper", "Lovelace", "Goldberg", "Borg", "Liskov"); 51 | } 52 | 53 | @Test 54 | public void getTotalPeople() { 55 | given(repository.count()).willReturn((long) people.size()); 56 | assertEquals(service.getTotalPeople(), people.size()); 57 | } 58 | 59 | @Test 60 | public void saveAllPeople() { 61 | given(repository.save(any(Person.class))) 62 | .willReturn(people.get(0), 63 | people.get(1), 64 | people.get(2), 65 | people.get(3), 66 | people.get(4)); 67 | 68 | List ids = service.savePeople( 69 | people.toArray(Person[]::new)); 70 | assertThat(ids).containsExactly(1, 2, 3, 14, 5); 71 | 72 | then(repository) 73 | .should(times(5)) 74 | .save(any(Person.class)); 75 | then(repository).shouldHaveNoMoreInteractions(); 76 | } 77 | 78 | @Test 79 | public void saveAllPeopleUsingAnswer() { 80 | given(repository.save(any(Person.class))) 81 | .willAnswer(invocation -> invocation.getArgument(0)); 82 | 83 | List ids = service.savePeople( 84 | people.toArray(Person[]::new)); 85 | assertThat(ids).containsExactly(1, 2, 3, 14, 5); 86 | 87 | then(repository) 88 | .should(times(5)) 89 | .save(any(Person.class)); 90 | then(repository).shouldHaveNoMoreInteractions(); 91 | } 92 | 93 | @Test 94 | public void useAnswer() { 95 | given(repository.save(any(Person.class))) 96 | .will(invocation -> invocation.getArgument(0)); 97 | 98 | List ids = service.savePeople(people.toArray(Person[]::new)); 99 | 100 | assertThat(ids).containsExactly(1, 2, 3, 14, 5); 101 | } 102 | 103 | @Test 104 | public void createPersonWithBadDateString() { 105 | DateTimeParseException exception = assertThrows(DateTimeParseException.class, 106 | () -> service.createPerson(1, "Grace", "Hopper", "12/09/1906")); 107 | assertThat(exception.getMessage()).contains("could not be parsed"); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/test/java/com/kousenit/hr/PersonServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.hr; 2 | 3 | import org.junit.jupiter.api.Disabled; 4 | import org.junit.jupiter.api.Test; 5 | import org.junit.jupiter.api.extension.ExtendWith; 6 | import org.mockito.ArgumentCaptor; 7 | import org.mockito.Captor; 8 | import org.mockito.InjectMocks; 9 | import org.mockito.Mock; 10 | import org.mockito.invocation.InvocationOnMock; 11 | import org.mockito.junit.jupiter.MockitoExtension; 12 | import org.mockito.stubbing.Answer; 13 | 14 | import java.time.LocalDate; 15 | import java.time.Month; 16 | import java.util.Arrays; 17 | import java.util.List; 18 | import java.util.Map; 19 | import java.util.Optional; 20 | import java.util.stream.Collectors; 21 | 22 | import static org.assertj.core.api.Assertions.assertThat; 23 | import static org.junit.jupiter.api.Assertions.*; 24 | import static org.mockito.AdditionalAnswers.returnsFirstArg; 25 | import static org.mockito.BDDMockito.given; 26 | import static org.mockito.BDDMockito.then; 27 | import static org.mockito.Mockito.*; 28 | 29 | @ExtendWith(MockitoExtension.class) 30 | public class PersonServiceTest { 31 | 32 | @Mock 33 | private PersonRepository repository; 34 | 35 | @InjectMocks 36 | private PersonService service; 37 | 38 | @Captor 39 | private ArgumentCaptor personArg; 40 | 41 | private final List people = List.of( 42 | new Person(1, "Grace", "Hopper", LocalDate.of(1906, Month.DECEMBER, 9)), 43 | new Person(2, "Ada", "Lovelace", LocalDate.of(1815, Month.DECEMBER, 10)), 44 | new Person(3, "Adele", "Goldberg", LocalDate.of(1945, Month.JULY, 7)), 45 | new Person(14, "Anita", "Borg", LocalDate.of(1949, Month.JANUARY, 17)), 46 | new Person(5, "Barbara", "Liskov", LocalDate.of(1939, Month.NOVEMBER, 7))); 47 | 48 | private final Map peopleMap = people.stream() 49 | .collect(Collectors.toMap(Person::getId, p -> p)); 50 | 51 | // Can't be done because JUnit 5 extension is _strict_ and 52 | // many of these tests don't call repository.findAll() 53 | // @BeforeEach 54 | // void setUp() { 55 | // when(repository.findAll()).thenReturn(people); 56 | // } 57 | 58 | @Test 59 | public void findMaxId() { 60 | when(repository.findAll()).thenReturn(people); 61 | 62 | assertThat(service.getHighestId()).isEqualTo(14); 63 | 64 | verify(repository).findAll(); 65 | } 66 | 67 | @Test 68 | public void findMaxId_BDD() { 69 | given(repository.findAll()).willReturn(people); 70 | 71 | assertThat(service.getHighestId()).isEqualTo(14); 72 | 73 | then(repository).should() 74 | .findAll(); 75 | // then(repository).should(times(1)).findAll(); 76 | } 77 | 78 | @Test 79 | void defaultImplementations() { 80 | PersonRepository mockRepo = mock(PersonRepository.class); 81 | assertAll( 82 | () -> assertNull(mockRepo.save(any(Person.class))), 83 | () -> assertTrue(mockRepo.findById(anyInt()) 84 | .isEmpty()), 85 | () -> assertTrue(mockRepo.findAll() 86 | .isEmpty()), 87 | () -> assertEquals(0, mockRepo.count()) 88 | ); 89 | } 90 | 91 | @Test 92 | void getLastNames_usingMockMethod() { 93 | PersonRepository mockRepo = mock(PersonRepository.class); 94 | when(mockRepo.findAll()).thenReturn(people); 95 | 96 | PersonService personService = new PersonService(mockRepo); 97 | 98 | List lastNames = personService.getLastNames(); 99 | 100 | assertThat(lastNames) 101 | .contains("Borg", "Goldberg", "Hopper", "Liskov", "Lovelace"); 102 | verify(mockRepo).findAll(); 103 | } 104 | 105 | @Test 106 | public void getLastNames_usingAnnotations() { 107 | when(repository.findAll()).thenReturn(people); 108 | 109 | assertThat(service.getLastNames()) 110 | .contains("Borg", "Goldberg", "Hopper", "Liskov", "Lovelace"); 111 | 112 | verify(repository).findAll(); 113 | } 114 | 115 | @Test 116 | public void getTotalPeople() { 117 | when(repository.count()) 118 | .thenReturn((long) people.size()); 119 | assertEquals(people.size(), service.getTotalPeople()); 120 | } 121 | 122 | @Test 123 | public void saveAllPeople() { 124 | // set expectations on the mock repository 125 | when(repository.save(any(Person.class))) 126 | .thenReturn(people.get(0), 127 | people.get(1), 128 | people.get(2), 129 | people.get(3), 130 | people.get(4)); 131 | 132 | // test the savePeople method 133 | List ids = service.savePeople(people.toArray(Person[]::new)); 134 | 135 | // check the results 136 | List actuals = people.stream() 137 | .map(Person::getId) 138 | .collect(Collectors.toList()); 139 | assertThat(ids).containsExactlyElementsOf(actuals); 140 | 141 | // verify the interaction between the service and the mock 142 | verify(repository, times(people.size())).save(any(Person.class)); 143 | verify(repository, never()).delete(any(Person.class)); 144 | } 145 | 146 | @SuppressWarnings("Convert2Lambda") 147 | @Test 148 | public void saveAllPeople_usingAnswer_anonInnerClass() { 149 | // Anonymous inner class 150 | when(repository.save(any(Person.class))) 151 | .thenAnswer(new Answer() { 152 | @Override 153 | public Person answer(InvocationOnMock invocation) { 154 | return invocation.getArgument(0); 155 | } 156 | }); 157 | 158 | // test the savePeople method 159 | List ids = service.savePeople(people.toArray(Person[]::new)); 160 | 161 | // check the results 162 | List actuals = people.stream() 163 | .map(Person::getId) 164 | .collect(Collectors.toList()); 165 | assertThat(ids).containsExactlyElementsOf(actuals); 166 | } 167 | 168 | @Test 169 | public void saveAllPeople_usingAnswer_lambdaExpression() { 170 | // Lambda expression implementation of Answer 171 | when(repository.save(any(Person.class))) 172 | .thenAnswer(invocation -> invocation.getArgument(0)); 173 | 174 | // test the savePeople method 175 | List ids = service.savePeople(people.toArray(Person[]::new)); 176 | 177 | // check the results 178 | List actuals = people.stream() 179 | .map(Person::getId) 180 | .collect(Collectors.toList()); 181 | assertThat(ids).containsExactlyElementsOf(actuals); 182 | } 183 | 184 | @Test 185 | public void saveAllPeople_usingAdditionalAnswers() { 186 | // set the expectations on the mock 187 | when(repository.save(any(Person.class))) 188 | .thenAnswer(returnsFirstArg()); 189 | 190 | // invoke the test method 191 | List ids = service.savePeople(people.toArray(Person[]::new)); 192 | 193 | // check the results 194 | List actuals = people.stream() 195 | .map(Person::getId) 196 | .collect(Collectors.toList()); 197 | assertThat(ids).containsExactlyElementsOf(actuals); 198 | } 199 | 200 | @Test 201 | public void savePersonThrowsException() { 202 | when(repository.save(any(Person.class))) 203 | .thenThrow(RuntimeException.class); 204 | 205 | assertThrows(RuntimeException.class, () -> service.savePeople(people.get(0))); 206 | } 207 | 208 | @Test 209 | public void createPerson() { 210 | when(repository.save(any(Person.class))) 211 | .thenAnswer(invocation -> invocation.getArgument(0)); 212 | 213 | Person hopper = people.get(0); 214 | Person person = service.createPerson( 215 | hopper.getId(), 216 | hopper.getFirst(), 217 | hopper.getLast(), 218 | hopper.getDob()); 219 | 220 | verify(repository).save(personArg.capture()); 221 | 222 | assertEquals(personArg.getValue(), hopper); 223 | assertEquals(hopper, person); 224 | } 225 | 226 | @Test 227 | public void createPersonUsingDateString() { 228 | Person hopper = people.get(0); 229 | 230 | when(repository.save(hopper)).thenReturn(hopper); // generalize this with an answer 231 | Person actual = service.createPerson(1, "Grace", "Hopper", "1906-12-09"); 232 | 233 | verify(repository).save(personArg.capture()); 234 | assertThat(personArg.getValue()).isEqualTo(hopper); 235 | assertThat(actual).isEqualTo(hopper); 236 | } 237 | 238 | @Test 239 | public void deleteAll() { 240 | when(repository.findAll()).thenReturn(people); 241 | 242 | doNothing().when(repository) 243 | .delete(any(Person.class)); 244 | 245 | service.deleteAll(); 246 | 247 | verify(repository, times(5)).delete(any(Person.class)); 248 | } 249 | 250 | @SuppressWarnings("ArraysAsListWithZeroOrOneArgument") 251 | @Test 252 | public void deleteAllWithNulls() { 253 | when(repository.findAll()).thenReturn( 254 | Arrays.asList((Person) null)); 255 | 256 | //when(repository.delete(null)).thenThrow(RuntimeException.class); 257 | doThrow(RuntimeException.class).when(repository) 258 | .delete(null); 259 | 260 | assertThrows(RuntimeException.class, () -> service.deleteAll()); 261 | 262 | verify(repository).delete(null); 263 | } 264 | 265 | @Test 266 | @Disabled("Do not use argThat with integers") 267 | public void findByIdThatDoesNotExist_argThat() { 268 | // More specific, custom matcher 269 | when(repository.findById(argThat(id -> id > 14))) 270 | .thenReturn(Optional.empty()); 271 | 272 | List personList = service.findByIds(999); 273 | assertTrue(personList.isEmpty()); 274 | 275 | verify(repository).findById(anyInt()); 276 | } 277 | 278 | 279 | @Test 280 | public void findByIdsThatDoNotExist_intThat() { 281 | // More specific, custom matcher 282 | when(repository.findById(intThat(id -> id > 14))) 283 | .thenReturn(Optional.empty()); 284 | 285 | List personList = service.findByIds(15, 42, 78, 999); 286 | assertTrue(personList.isEmpty()); 287 | 288 | verify(repository, times(4)).findById(anyInt()); 289 | } 290 | 291 | @Test 292 | public void findByIdsThatDoExist() { 293 | int maxId = peopleMap.keySet() 294 | .stream() 295 | .max(Integer::compareTo) 296 | .orElse(0); 297 | when(repository.findById(intThat(id -> id <= maxId))) 298 | .thenAnswer(invocation -> { 299 | int id = invocation.getArgument(0); 300 | return Optional.ofNullable(peopleMap.get(id)); 301 | }); 302 | 303 | List personList = service.findByIds(1, 3, 5, maxId); 304 | assertThat(personList).containsExactlyElementsOf( 305 | List.of(peopleMap.get(1), peopleMap.get(3), 306 | peopleMap.get(5), peopleMap.get(maxId))); 307 | } 308 | 309 | @Test 310 | void findByIds_explicitWhens() { 311 | when(repository.findById(0)) 312 | .thenReturn(Optional.of(people.get(0))); 313 | when(repository.findById(1)) 314 | .thenReturn(Optional.of(people.get(1))); 315 | when(repository.findById(2)) 316 | .thenReturn(Optional.of(people.get(2))); 317 | when(repository.findById(3)) 318 | .thenReturn(Optional.of(people.get(3))); 319 | when(repository.findById(4)) 320 | .thenReturn(Optional.of(people.get(4))); 321 | when(repository.findById(5)) 322 | .thenReturn(Optional.empty()); 323 | 324 | List personList = service.findByIds(0, 1, 2, 3, 4, 5); 325 | assertThat(personList).containsExactlyElementsOf(people); 326 | } 327 | 328 | @SuppressWarnings("unchecked") 329 | @Test 330 | void findByIds_thenReturnWithMultipleArgs() { 331 | when(repository.findById(anyInt())).thenReturn( 332 | Optional.of(people.get(0)), 333 | Optional.of(people.get(1)), 334 | Optional.of(people.get(2)), 335 | Optional.of(people.get(3)), 336 | Optional.of(people.get(4)), 337 | Optional.empty()); 338 | 339 | List personList = service.findByIds(0, 1, 2, 3, 4, 5); 340 | assertThat(personList).isEqualTo(people); 341 | } 342 | 343 | @Test 344 | void testInMemoryPersonRepository() { 345 | PersonRepository personRepo = new InMemoryPersonRepository(); 346 | PersonService personService = new PersonService(personRepo); 347 | 348 | personService.savePeople(people.toArray(Person[]::new)); 349 | assertThat(personRepo.findAll()).isEqualTo(people); 350 | } 351 | 352 | @Test 353 | void testMockOfFinalMethod() { 354 | PersonRepository personRepo = mock(InMemoryPersonRepository.class); 355 | 356 | // Set expectations on the mock 357 | when(personRepo.save(any(Person.class))) 358 | .thenAnswer(invocation -> invocation.getArgument(0)); 359 | 360 | // Inject the mock into the class under test 361 | PersonService personService = new PersonService(personRepo); 362 | 363 | // Test the savePeople method 364 | List ids = personService.savePeople(people.toArray(Person[]::new)); 365 | assertThat(ids).containsExactly(1, 2, 3, 14, 5); 366 | 367 | // Verify the save method in the mock was called as expected 368 | verify(personRepo, times(5)).save(any(Person.class)); 369 | } 370 | 371 | @Test 372 | void spyOnRepository() { 373 | PersonRepository personRepo = spy(new InMemoryPersonRepository()); 374 | PersonService personService = new PersonService(personRepo); 375 | 376 | personService.savePeople(people.toArray(Person[]::new)); 377 | assertThat(personRepo.findAll()).isEqualTo(people); 378 | 379 | verify(personRepo, times(people.size())).save(any(Person.class)); 380 | } 381 | 382 | @Test 383 | void asyncSavePerson() { 384 | Person firstPerson = people.get(0); 385 | long delay = 100; 386 | 387 | // Call the method under test 388 | service.asyncSavePerson(firstPerson, delay); 389 | 390 | // Verify that the save method was called on the repo with the person 391 | verify(repository, timeout(2 * delay)).save(firstPerson); 392 | } 393 | 394 | } -------------------------------------------------------------------------------- /src/test/java/com/kousenit/hr/PersonTest.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.hr; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.time.LocalDate; 6 | import java.time.Month; 7 | 8 | import static org.junit.jupiter.api.Assertions.*; 9 | 10 | public class PersonTest { 11 | private final Person person1 = new Person(1, "Grace", "Hopper", 12 | LocalDate.of(1906, Month.DECEMBER, 9)); 13 | private final Person person1Copy = new Person(1, "Grace", "Hopper", 14 | LocalDate.of(1906, Month.DECEMBER, 9)); 15 | private final Person person2 = new Person(2, "Ada", "Lovelace", 16 | LocalDate.of(1815, Month.DECEMBER, 10)); 17 | private final Person personNullId = new Person(null, "Grace", "Hopper", 18 | LocalDate.of(1906, Month.DECEMBER, 9)); 19 | private final Person personNullFirst = new Person(1, null, "Hopper", 20 | LocalDate.of(1906, Month.DECEMBER, 9)); 21 | private final Person personNullLast = new Person(1, "Grace", null, 22 | LocalDate.of(1906, Month.DECEMBER, 9)); 23 | private final Person personNullDob = new Person(1, "Grace", "Hopper", null); 24 | 25 | @Test 26 | void testEqualsWithSameReference() { 27 | // Test case where object equals itself (same reference) 28 | assertTrue(person1.equals(person1)); 29 | } 30 | 31 | @Test 32 | void testEqualsWithNull() { 33 | // Test case with null 34 | assertFalse(person1.equals(null)); 35 | } 36 | 37 | @Test 38 | void testEqualsWithDifferentClass() { 39 | // Test case with different class 40 | assertFalse(person1.equals("Not a Person")); 41 | } 42 | 43 | @Test 44 | void testEqualsWithEqualObjects() { 45 | // Test case with equal objects 46 | assertTrue(person1.equals(person1Copy)); 47 | assertTrue(person1Copy.equals(person1)); 48 | } 49 | 50 | @Test 51 | void testEqualsWithDifferentObjects() { 52 | // Test case with different objects 53 | assertFalse(person1.equals(person2)); 54 | assertFalse(person2.equals(person1)); 55 | } 56 | 57 | @Test 58 | void testEqualsWithNullFields() { 59 | // Test cases with null fields 60 | assertFalse(person1.equals(personNullId)); 61 | assertFalse(personNullId.equals(person1)); 62 | 63 | assertFalse(person1.equals(personNullFirst)); 64 | assertFalse(personNullFirst.equals(person1)); 65 | 66 | assertFalse(person1.equals(personNullLast)); 67 | assertFalse(personNullLast.equals(person1)); 68 | 69 | assertFalse(person1.equals(personNullDob)); 70 | assertFalse(personNullDob.equals(person1)); 71 | } 72 | 73 | @Test 74 | void testHashCode() { 75 | // Equal objects should have the same hash code 76 | assertEquals(person1.hashCode(), person1Copy.hashCode()); 77 | 78 | // Different objects should have different hash codes 79 | assertNotEquals(person1.hashCode(), person2.hashCode()); 80 | } 81 | 82 | @Test 83 | void testGetters() { 84 | assertEquals(1, person1.getId()); 85 | assertEquals("Grace", person1.getFirst()); 86 | assertEquals("Hopper", person1.getLast()); 87 | assertEquals(LocalDate.of(1906, Month.DECEMBER, 9), person1.getDob()); 88 | } 89 | 90 | @Test 91 | void testToString() { 92 | String expected = "Person[id=1, first=Grace, last=Hopper, dob=1906-12-09]"; 93 | assertEquals(expected, person1.toString()); 94 | } 95 | } -------------------------------------------------------------------------------- /src/test/java/com/kousenit/inorder/OrderServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.inorder; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertTrue; 4 | import static org.mockito.Mockito.*; 5 | 6 | import org.junit.jupiter.api.Test; 7 | import org.junit.jupiter.api.extension.ExtendWith; 8 | import org.mockito.InOrder; 9 | import org.mockito.InjectMocks; 10 | import org.mockito.Mock; 11 | import org.mockito.junit.jupiter.MockitoExtension; 12 | 13 | @ExtendWith(MockitoExtension.class) 14 | public class OrderServiceTest { 15 | 16 | @Mock 17 | private PaymentService paymentServiceMock; 18 | 19 | @Mock 20 | private ShippingService shippingServiceMock; 21 | 22 | @InjectMocks 23 | private OrderService orderService; 24 | 25 | @Test 26 | public void processOrder() { 27 | // set the expectations on the mocks 28 | when(paymentServiceMock.processPayment(anyDouble())).thenReturn(true); 29 | when(shippingServiceMock.shipProduct(anyString())).thenReturn(true); 30 | 31 | // call the method to test 32 | double amount = 100.00; 33 | String address = "1313 Mockingbird Lane, New York, NY 10001"; 34 | assertTrue(orderService.processOrder(amount, address)); 35 | 36 | // verify that the mocked methods were called in the right order 37 | InOrder inOrder = inOrder(paymentServiceMock, shippingServiceMock); 38 | inOrder.verify(paymentServiceMock).processPayment(amount); 39 | inOrder.verify(shippingServiceMock).shipProduct(address); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/com/kousenit/inorder/PaymentServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.inorder; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.mockito.InOrder; 5 | 6 | import static org.junit.jupiter.api.Assertions.assertTrue; 7 | import static org.mockito.ArgumentMatchers.anyDouble; 8 | import static org.mockito.Mockito.*; 9 | 10 | class PaymentServiceTest { 11 | 12 | @Test 13 | void processPayment() { 14 | // create the mock 15 | PaymentProcessor processor = mock(PaymentProcessor.class); 16 | 17 | // set expectations on the mock 18 | when(processor.authorizePayment(anyDouble())).thenReturn(true); 19 | when(processor.capturePayment()).thenReturn(true); 20 | 21 | // inject the mock into the class under test 22 | PaymentService service = new PaymentService(processor); 23 | 24 | // call method under test 25 | assertTrue(service.processPayment(100.0)); 26 | 27 | // verify the methods on the mock were called in the right order 28 | InOrder inOrder = inOrder(processor); 29 | inOrder.verify(processor).authorizePayment(anyDouble()); 30 | inOrder.verify(processor).capturePayment(); 31 | } 32 | } -------------------------------------------------------------------------------- /src/test/java/com/kousenit/pubsub/PublisherTest.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.pubsub; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.Disabled; 5 | import org.junit.jupiter.api.DisplayName; 6 | import org.junit.jupiter.api.Test; 7 | import org.mockito.InOrder; 8 | 9 | import static org.mockito.Mockito.*; 10 | 11 | class PublisherTest { 12 | private final Publisher pub = new Publisher(); 13 | private final Subscriber sub1 = mock(Subscriber.class); 14 | private final Subscriber sub2 = mock(Subscriber.class); 15 | 16 | @BeforeEach 17 | void setUp() { 18 | pub.subscribe(sub1); 19 | pub.subscribe(sub2); 20 | } 21 | 22 | @Test 23 | void publisherSendsMessageToAllSubscribers() { 24 | pub.send("Hello"); 25 | 26 | verify(sub1).onNext("Hello"); 27 | verify(sub2).onNext("Hello"); 28 | } 29 | 30 | @Test 31 | void handleMisbehavingSubscribers() { 32 | // Does not compile (because onNext returns void): 33 | // when(sub1.onNext(anyString())).thenThrow(new RuntimeException("Oops")); 34 | 35 | // sub1 throws an exception 36 | doThrow(RuntimeException.class).when(sub1).onNext(anyString()); 37 | doNothing().when(sub2).onNext(anyString()); // legal, but redundant (it's the default) 38 | 39 | pub.send("message 1"); 40 | pub.send("message 2"); 41 | 42 | // both subscribers still received the messages 43 | verify(sub1, times(2)).onNext(matches("message \\d")); 44 | verify(sub2, times(2)).onNext(anyString()); 45 | } 46 | 47 | @Test 48 | void testSendInOrder() { // Is this a good idea?? Overspecifying the implementation 49 | pub.send("Hello"); 50 | 51 | InOrder inorder = inOrder(sub1, sub2); 52 | inorder.verify(sub1).onNext("Hello"); 53 | inorder.verify(sub2).onNext("Hello"); 54 | } 55 | 56 | @Test @DisplayName("Test send in parallel") 57 | @Disabled("This test fails because the order of the calls is not guaranteed") 58 | void testSendParallelCausesProblemsWithOrder() { 59 | pub.sendParallel("Hello"); 60 | 61 | InOrder inorder = inOrder(sub1, sub2); 62 | inorder.verify(sub1).onNext("Hello"); 63 | inorder.verify(sub2).onNext("Hello"); 64 | } 65 | 66 | @Test 67 | void testSendParallel_withoutOrderVerification() { 68 | pub.sendParallel("Hello"); 69 | 70 | // Verify both subscribers received the message (without order verification) 71 | verify(sub1).onNext("Hello"); 72 | verify(sub2).onNext("Hello"); 73 | } 74 | 75 | @Test 76 | void testSendParallel_withException() { 77 | // Configure sub1 to throw an exception 78 | doThrow(RuntimeException.class).when(sub1).onNext(anyString()); 79 | 80 | // Call should not throw the exception outside the method 81 | pub.sendParallel("Message"); 82 | 83 | // Verify both subscribers were called 84 | verify(sub1).onNext("Message"); 85 | verify(sub2).onNext("Message"); 86 | } 87 | 88 | @Test 89 | void publisherSendsMessageWithAPattern() { 90 | pub.send("Message 1"); 91 | pub.send("Message 2"); 92 | 93 | // Check for any string 94 | verify(sub1, times(2)).onNext(anyString()); 95 | verify(sub2, times(2)).onNext(anyString()); 96 | 97 | // Check for specific string pattern 98 | verify(sub1, times(2)).onNext( 99 | argThat(s -> s.matches("Message \\d"))); 100 | verify(sub2, times(2)).onNext( 101 | argThat(s -> s.matches("Message [1-2]"))); 102 | 103 | // Simpler, without custom matcher 104 | verify(sub1, times(2)).onNext(matches("Message \\d")); 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/test/java/com/kousenit/simple/AddingMachineAnnotationTest.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.simple; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.mockito.InjectMocks; 6 | import org.mockito.Mock; 7 | import org.mockito.junit.MockitoJUnitRunner; 8 | 9 | import java.util.List; 10 | import java.util.stream.Stream; 11 | 12 | import static org.junit.Assert.assertEquals; 13 | import static org.mockito.Mockito.*; 14 | 15 | @RunWith(MockitoJUnitRunner.class) 16 | public class AddingMachineAnnotationTest { 17 | @Mock 18 | private List mockList; 19 | 20 | @InjectMocks 21 | private AddingMachine machine; 22 | 23 | // @Rule 24 | // public MockitoRule rule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS); 25 | 26 | // @Before 27 | // public void setUp() { 28 | // MockitoAnnotations.openMocks(this); 29 | // } 30 | 31 | @Test 32 | public void getTotalUsingLoop() { 33 | when(mockList.size()).thenReturn(3); 34 | when(mockList.get(0)).thenReturn(1); 35 | when(mockList.get(1)).thenReturn(2); 36 | when(mockList.get(2)).thenReturn(3); 37 | 38 | // Only requires the stub behavior, 39 | // i.e., that the get(i) methods return the expected values 40 | assertEquals(6, machine.getTotalUsingLoop()); 41 | 42 | // Verify the protocol -- that the mock methods are called 43 | // the right number of times 44 | verify(mockList).size(); 45 | verify(mockList, times(3)).get(anyInt()); 46 | verify(mockList, never()).clear(); 47 | } 48 | 49 | @Test 50 | public void getTotalUsingStream() { 51 | when(mockList.stream()).thenReturn(Stream.of(1, 2, 3)); 52 | 53 | // Only requires the stub behavior, 54 | assertEquals(6, machine.getTotalUsingStream()); 55 | 56 | // Verify the protocol -- that the mock methods are called 57 | // the right number of times 58 | verify(mockList).stream(); 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /src/test/java/com/kousenit/simple/AddingMachineJUnit5Test.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.simple; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.junit.jupiter.api.extension.ExtendWith; 5 | import org.mockito.InOrder; 6 | import org.mockito.InjectMocks; 7 | import org.mockito.Mock; 8 | import org.mockito.junit.jupiter.MockitoExtension; 9 | 10 | import java.util.List; 11 | import java.util.stream.Stream; 12 | 13 | import static org.junit.jupiter.api.Assertions.assertAll; 14 | import static org.junit.jupiter.api.Assertions.assertEquals; 15 | import static org.mockito.Mockito.*; 16 | 17 | @SuppressWarnings({"ResultOfMethodCallIgnored", "CommentedOutCode"}) 18 | @ExtendWith(MockitoExtension.class) 19 | public class AddingMachineJUnit5Test { 20 | @Mock 21 | private List mockList; 22 | 23 | @InjectMocks 24 | private AddingMachine machine; 25 | 26 | // @Spy 27 | // private List spyList; 28 | 29 | @Test 30 | public void getTotalUsingLoop() { 31 | when(mockList.size()).thenReturn(3); 32 | when(mockList.get(0)).thenReturn(1); 33 | when(mockList.get(1)).thenReturn(2); 34 | when(mockList.get(2)).thenReturn(3); 35 | 36 | // Only requires the stub behavior, 37 | // i.e., that the get(i) methods return the expected values 38 | assertEquals(1 + 2 + 3, machine.getTotalUsingLoop()); 39 | 40 | // Verify the protocol -- that the mock methods are called 41 | // the right number of times in the right order 42 | InOrder inOrder = inOrder(mockList); 43 | 44 | inOrder.verify(mockList).size(); 45 | inOrder.verify(mockList, times(3)).get(anyInt()); 46 | } 47 | 48 | @Test 49 | public void getTotalUsingAnyInt() { 50 | when(mockList.size()).thenReturn(3); 51 | // when(mockList.get(anyInt())) 52 | // .thenReturn(1) 53 | // .thenReturn(2) 54 | // .thenReturn(3); 55 | 56 | when(mockList.get(anyInt())) 57 | .thenReturn(1, 2, 3); 58 | 59 | // Only requires the stub behavior, 60 | // i.e., that the get(i) methods return the expected values 61 | // assertThat(nc.getTotalUsingLoop(), is(equalTo(6))); 62 | assertEquals(1 + 2 + 3, machine.getTotalUsingLoop()); 63 | 64 | // Verify the protocol -- that the mock methods are called 65 | // the right number of times in the right order 66 | InOrder inOrder = inOrder(mockList); 67 | 68 | assertAll("verify stub methods called right num of times in proper order", 69 | () -> inOrder.verify(mockList).size(), 70 | () -> inOrder.verify(mockList, times(3)).get(anyInt())); 71 | } 72 | 73 | @Test 74 | public void getTotalUsingStream() { 75 | when(mockList.stream()).thenReturn(Stream.of(1, 2, 3)); 76 | 77 | // Only requires the stub behavior, 78 | assertEquals(6, machine.getTotalUsingStream()); 79 | 80 | // Verify the protocol -- that the mock methods are called 81 | // the right number of times 82 | verify(mockList).stream(); 83 | } 84 | 85 | @Test 86 | void getTotalUsingLoopCustomMatcher() { 87 | when(mockList.size()).thenReturn(3); 88 | when(mockList.get(anyInt())) 89 | .thenReturn(1, 2, 3); 90 | 91 | assertEquals(1 + 2 + 3, machine.getTotalUsingLoop()); 92 | 93 | // Custom matcher: intThat takes an ArgumentMatcher 94 | verify(mockList, times(3)).get(intThat(n -> n < 3)); 95 | 96 | // This line throws an NPE. Don't use argThat for primitives. 97 | // verify(mockList, times(3)).get(argThat(n -> n < 3)); 98 | } 99 | } -------------------------------------------------------------------------------- /src/test/java/com/kousenit/simple/AddingMachineTest.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.simple; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.Arrays; 6 | import java.util.List; 7 | import java.util.stream.Stream; 8 | 9 | import static org.junit.Assert.*; 10 | import static org.mockito.BDDMockito.given; 11 | import static org.mockito.BDDMockito.then; 12 | import static org.mockito.Mockito.*; 13 | 14 | @SuppressWarnings("ResultOfMethodCallIgnored") 15 | public class AddingMachineTest { 16 | 17 | @Test 18 | public void getTotalUsingLoop() { 19 | // Create a stubbed list 20 | // Use no-arg version of mock() to avoid 21 | // compiler warning about unchecked cast 22 | List mockList = mock(); 23 | 24 | // Set the expectations on the stub 25 | when(mockList.size()).thenReturn(3); 26 | when(mockList.get(0)).thenReturn(1); 27 | when(mockList.get(1)).thenReturn(2); 28 | when(mockList.get(2)).thenReturn(3); 29 | 30 | // Inject the stub into the class we want to test 31 | AddingMachine machine = new AddingMachine(mockList); 32 | 33 | // Test the method we care about 34 | assertEquals(1 + 2 + 3, machine.getTotalUsingLoop()); 35 | 36 | // Verify the protocol between AddingMachine and the stubbed list 37 | verify(mockList).size(); 38 | verify(mockList, times(3)).get(anyInt()); 39 | } 40 | 41 | @Test 42 | public void getTotalUsingLoop_BDD() { 43 | // Create a stubbed list 44 | List mockList = mock(); 45 | 46 | // Set the expectations on the stub 47 | given(mockList.size()).willReturn(3); 48 | given(mockList.get(anyInt())).willReturn(1, 2, 3); 49 | 50 | // Inject the stub into the class we want to test 51 | AddingMachine machine = new AddingMachine(mockList); 52 | 53 | // Test the method we care about 54 | assertEquals(1 + 2 + 3, machine.getTotalUsingLoop()); 55 | 56 | // Verify the protocol between AddingMachine and the stubbed list 57 | then(mockList).should().size(); 58 | then(mockList).should(times(3)) 59 | .get(intThat(n -> n >= 0 && n < 3)); 60 | // then(mockList).should(times(0)).remove(anyInt()); 61 | then(mockList).shouldHaveNoMoreInteractions(); 62 | } 63 | 64 | @Test // @SuppressWarnings("unchecked") 65 | public void getTotalUsingIterable() { 66 | List mockList = mock(); 67 | 68 | when(mockList.iterator()).thenReturn( 69 | Arrays.asList(1, 2, 3).iterator()); 70 | 71 | AddingMachine machine = new AddingMachine(mockList); 72 | assertEquals(1 + 2 + 3, machine.getTotalUsingIterable()); 73 | 74 | verify(mockList).iterator(); 75 | } 76 | 77 | @Test 78 | public void getTotalUsingStream() { 79 | List mockList = mock(); 80 | when(mockList.stream()).thenReturn(Stream.of(1, 2, 3)); 81 | // when(mockList.size()).thenReturn(3); // In JUnit 5, which is strict, this is not allowed 82 | 83 | AddingMachine machine = new AddingMachine(mockList); 84 | 85 | assertEquals(1 + 2 + 3, machine.getTotalUsingStream()); 86 | 87 | verify(mockList).stream(); 88 | } 89 | 90 | @Test 91 | public void getTotalUsingMockedIntegerList() { 92 | // Write our own mock implementation of List 93 | // Only the size() and get(0), get(1), and get(2) methods are stubbed 94 | List mockList = new MockListOfInteger(); 95 | 96 | // Inject the stub into the class we want to test 97 | AddingMachine machine = new AddingMachine(mockList); 98 | 99 | // Test the method we care about 100 | assertEquals(1 + 2 + 3, machine.getTotalUsingLoop()); 101 | } 102 | 103 | @Test // Not using Mockito at all 104 | public void getTotalWithRealList() { 105 | List realList = List.of(1, 2, 3); 106 | 107 | AddingMachine machine = new AddingMachine(realList); 108 | 109 | assertEquals(1 + 2 + 3, machine.getTotalUsingLoop()); 110 | assertEquals(1 + 2 + 3, machine.getTotalUsingIterable()); 111 | assertEquals(1 + 2 + 3, machine.getTotalUsingStream()); 112 | 113 | // No built-in way to verify the method calls on real list 114 | } 115 | 116 | @Test 117 | public void spyOnList() { 118 | // Spy on a real list 119 | List spyList = spy(Arrays.asList(1, 2, 3)); 120 | 121 | AddingMachine machine = new AddingMachine(spyList); 122 | 123 | assertEquals(1 + 2 + 3, machine.getTotalUsingLoop()); 124 | assertEquals(1 + 2 + 3, machine.getTotalUsingIterable()); 125 | assertEquals(1 + 2 + 3, machine.getTotalUsingStream()); 126 | 127 | // Can verify a spy 128 | verify(spyList).size(); 129 | verify(spyList, times(3)).get(anyInt()); 130 | verify(spyList).iterator(); 131 | verify(spyList).stream(); 132 | } 133 | 134 | @Test 135 | public void partialMockOfList() { 136 | // Spy on a real list 137 | List spyList = spy(List.of()); 138 | 139 | // Stub the size() method 140 | when(spyList.size()).thenReturn(3); 141 | 142 | // assertFalse(spyList.isEmpty()); // Uh oh. Is it empty or not? 143 | } 144 | } -------------------------------------------------------------------------------- /src/test/java/com/kousenit/simple/DocsTest.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.simple; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.List; 6 | 7 | import static org.mockito.Mockito.mock; 8 | import static org.mockito.Mockito.verify; 9 | 10 | public class DocsTest { 11 | // "Let's verify some behaviour!" 12 | // https://javadoc.io/static/org.mockito/mockito-core/5.3.1/org/mockito/Mockito.html#verification 13 | @Test 14 | void testFromDocumentation() { 15 | //mock creation 16 | List mockedList = mock(); 17 | 18 | //using mock object 19 | mockedList.add("one"); 20 | mockedList.clear(); 21 | 22 | //verification 23 | verify(mockedList).add("one"); 24 | verify(mockedList).clear(); 25 | 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/com/kousenit/simple/HelloServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.simple; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.junit.jupiter.api.extension.ExtendWith; 5 | import org.mockito.InjectMocks; 6 | import org.mockito.Mock; 7 | import org.mockito.junit.jupiter.MockitoExtension; 8 | 9 | import static org.junit.jupiter.api.Assertions.assertEquals; 10 | import static org.mockito.ArgumentMatchers.anyString; 11 | import static org.mockito.ArgumentMatchers.eq; 12 | import static org.mockito.Mockito.when; 13 | 14 | @ExtendWith(MockitoExtension.class) 15 | class HelloServiceTest { 16 | 17 | @Mock 18 | private TranslateService translateService; 19 | 20 | @InjectMocks 21 | private HelloService helloService; 22 | 23 | // Full end-to-end test 24 | @Test 25 | void testGreet() { 26 | HelloService hello = new HelloService(new TranslateService()); 27 | String greeting = hello.greet("Dolly", "en"); 28 | assertEquals("en translation: Hello, Dolly!", greeting); 29 | } 30 | 31 | @Test 32 | void testGreetWithFixedValues() { 33 | when(translateService.translate("Hello, Dolly!", "en")) 34 | .thenReturn("en translation: Hello, Dolly!"); 35 | 36 | String greeting = helloService.greet("Dolly", "en"); 37 | assertEquals("en translation: Hello, Dolly!", greeting); 38 | } 39 | 40 | // Mock the TranslateService for any string, but works only for "en" 41 | @Test 42 | void testGreetWithMock() { 43 | when(translateService.translate(anyString(), anyString())) 44 | .thenReturn("en translation: Hello, Dolly!"); 45 | 46 | String greeting = helloService.greet("Dolly", "en"); 47 | assertEquals("en translation: Hello, Dolly!", greeting); 48 | } 49 | 50 | // Mock the TranslateService for "en" only 51 | @Test 52 | void testGreetWithMockForEnglish() { 53 | when(translateService.translate(anyString(), eq("en"))) 54 | .thenReturn("en translation: Hello, Dolly!"); 55 | 56 | String greeting = helloService.greet("Dolly", "en"); 57 | assertEquals("en translation: Hello, Dolly!", greeting); 58 | } 59 | 60 | // Mock the TranslateService for any language, using answer 61 | @Test 62 | void testGreetWithMockForEnglishWithAnswer() { 63 | when(translateService.translate(anyString(), anyString())) 64 | .thenAnswer(invocation -> invocation.getArgument(1) + 65 | " translation: Hello, Dolly!"); 66 | 67 | String greeting = helloService.greet("Dolly", "en"); 68 | assertEquals("en translation: Hello, Dolly!", greeting); 69 | 70 | greeting = helloService.greet("Dolly", "fr"); 71 | assertEquals("fr translation: Hello, Dolly!", greeting); 72 | } 73 | 74 | } -------------------------------------------------------------------------------- /src/test/java/com/kousenit/simple/LoggingDemoTests.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.simple; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.logging.Logger; 6 | 7 | import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; 8 | import static org.mockito.Mockito.*; 9 | 10 | public class LoggingDemoTests { 11 | private final Logger logger = 12 | Logger.getLogger(LoggingDemoTests.class.getName()); 13 | 14 | @Test 15 | public void testLogWithSpy() { 16 | // Spy on the logger 17 | Logger spy = spy(logger); 18 | 19 | // Inject the logger into the class under test 20 | LoggingDemo demo = new LoggingDemo(spy); 21 | 22 | // Invoke the method to test 23 | demo.doStuff("Hello, world!"); 24 | 25 | // Verify that the info method of the logger was called 26 | verify(spy).info("Hello, world!"); 27 | } 28 | 29 | @Test 30 | public void testLog() { 31 | LoggingDemo demo = new LoggingDemo(logger); 32 | assertDoesNotThrow( 33 | () -> demo.doStuff("Hello, world!")); 34 | 35 | // Nothing to test, because both 36 | // doStuff and info methods return void 37 | } 38 | 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/test/java/com/kousenit/simple/LoggingStandardErrorTests.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.simple; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.io.ByteArrayOutputStream; 6 | import java.io.PrintStream; 7 | import java.util.logging.Logger; 8 | 9 | import static org.assertj.core.api.Assertions.assertThat; 10 | 11 | public class LoggingStandardErrorTests { 12 | @Test 13 | void testLogWithStnErr() { 14 | Logger logger = Logger.getLogger(LoggingDemoTests.class.getName()); 15 | LoggingDemo demo = new LoggingDemo(logger); 16 | 17 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 18 | ByteArrayOutputStream err = new ByteArrayOutputStream(); 19 | 20 | System.setOut(new PrintStream(out)); 21 | System.setErr(new PrintStream(err)); 22 | 23 | demo.doStuff("Hello, world!"); 24 | 25 | assertThat(out.toString()).contains("Doing useful stuff: Hello, world!"); 26 | assertThat(err.toString()).contains("INFO: Hello, world!"); 27 | 28 | System.setOut(System.out); 29 | System.setErr(System.err); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/test/java/com/kousenit/simple/LoggingWithOutputCaptureTest.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.simple; 2 | 3 | import org.junit.jupiter.api.Disabled; 4 | import org.junit.jupiter.api.Test; 5 | import org.junit.jupiter.api.extension.ExtendWith; 6 | import org.springframework.boot.test.system.CapturedOutput; 7 | import org.springframework.boot.test.system.OutputCaptureExtension; 8 | 9 | import java.util.logging.Logger; 10 | 11 | import static org.assertj.core.api.Assertions.assertThat; 12 | 13 | @Disabled("Working locally, but failing as a GitHub Action") 14 | @ExtendWith(OutputCaptureExtension.class) 15 | public class LoggingWithOutputCaptureTest { 16 | @Test 17 | void testLogWithOutputCapture(CapturedOutput outputCapture) { 18 | Logger logger = Logger.getLogger(LoggingDemoTests.class.getName()); 19 | LoggingDemo demo = new LoggingDemo(logger); 20 | demo.doStuff("Hello, world!"); 21 | 22 | assertThat(outputCapture.getOut()) 23 | .contains("Doing useful stuff: Hello, world!"); 24 | assertThat(outputCapture.getErr()) 25 | .contains("INFO: Hello, world!"); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/com/kousenit/simple/MockFinalTypesTests.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.simple; 2 | 3 | import org.junit.jupiter.api.Disabled; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.time.LocalDate; 7 | import java.time.Month; 8 | 9 | import static org.assertj.core.api.Assertions.assertThat; 10 | import static org.mockito.Mockito.*; 11 | 12 | @SuppressWarnings("CommentedOutCode") 13 | public class MockFinalTypesTests { 14 | @Test 15 | public void mockFinalClassLocalDate() { 16 | LocalDate mockDate = mock(LocalDate.class); 17 | 18 | when(mockDate.toString()).thenReturn("1969-07-20"); 19 | when(mockDate.getYear()).thenReturn(1969); 20 | 21 | assertThat(mockDate).hasToString("1969-07-20"); 22 | int year = mockDate.getYear(); 23 | assertThat(year).isEqualTo(1969); 24 | 25 | verify(mockDate).getYear(); 26 | } 27 | 28 | @Test 29 | public void mockAnEnum() { 30 | Month mockMonth = mock(Month.class); 31 | 32 | when(mockMonth.getValue()).thenReturn(7); 33 | when(mockMonth.name()).thenReturn("July"); 34 | 35 | assertThat(mockMonth.name()).isEqualTo("July"); 36 | int value = mockMonth.getValue(); 37 | assertThat(value).isEqualTo(7); 38 | 39 | verify(mockMonth).getValue(); 40 | verify(mockMonth).name(); 41 | } 42 | 43 | @Test 44 | @Disabled("Mockito cannot mock wrapper types, String.class, or Class.class") 45 | public void mockFinalClassString() { 46 | /* 47 | String mockString = mock(String.class); 48 | 49 | when(mockString).thenReturn("hello"); 50 | when(mockString.length()).thenReturn(5); 51 | 52 | assertThat(mockString).hasToString("hello"); 53 | int length = mockString.length(); 54 | assertThat(length).isEqualTo(5); 55 | 56 | verify(mockString).length(); 57 | */ 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/test/java/com/kousenit/simple/MockListOfInteger.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.simple; 2 | 3 | import java.util.Collection; 4 | import java.util.Iterator; 5 | import java.util.List; 6 | import java.util.ListIterator; 7 | 8 | // Our own mocked list, to return a list containing the values 1, 2, 3 9 | // Only two methods are implemented: size, get(i) 10 | public class MockListOfInteger implements List { 11 | @Override 12 | public int size() { 13 | return 3; 14 | } 15 | 16 | @Override 17 | public Integer get(int index) { 18 | if (index == 0) return 1; 19 | if (index == 1) return 2; 20 | if (index == 2) return 3; 21 | return null; 22 | } 23 | 24 | @Override 25 | public boolean isEmpty() { 26 | return false; 27 | } 28 | 29 | @Override 30 | public boolean contains(Object o) { 31 | return false; 32 | } 33 | 34 | @Override 35 | public Iterator iterator() { 36 | return new Iterator<>() { 37 | @Override 38 | public boolean hasNext() { 39 | return false; 40 | } 41 | 42 | @Override 43 | public Integer next() { 44 | return null; 45 | } 46 | }; 47 | } 48 | 49 | @Override 50 | public Object[] toArray() { 51 | return new Object[0]; 52 | } 53 | 54 | @Override 55 | public T[] toArray(T[] a) { 56 | return a; 57 | } 58 | 59 | @Override 60 | public boolean add(Integer integer) { 61 | return true; 62 | } 63 | 64 | @Override 65 | public boolean remove(Object o) { 66 | return false; 67 | } 68 | 69 | @Override 70 | public boolean containsAll(Collection c) { 71 | return false; 72 | } 73 | 74 | @Override 75 | public boolean addAll(Collection c) { 76 | return false; 77 | } 78 | 79 | @Override 80 | public boolean addAll(int index, Collection c) { 81 | return false; 82 | } 83 | 84 | @Override 85 | public boolean removeAll(Collection c) { 86 | return false; 87 | } 88 | 89 | @Override 90 | public boolean retainAll(Collection c) { 91 | return false; 92 | } 93 | 94 | @Override 95 | public void clear() { 96 | 97 | } 98 | 99 | @Override 100 | public Integer set(int index, Integer element) { 101 | return null; 102 | } 103 | 104 | @Override 105 | public void add(int index, Integer element) { 106 | 107 | } 108 | 109 | @Override 110 | public Integer remove(int index) { 111 | return null; 112 | } 113 | 114 | @Override 115 | public int indexOf(Object o) { 116 | return 0; 117 | } 118 | 119 | @Override 120 | public int lastIndexOf(Object o) { 121 | return 0; 122 | } 123 | 124 | @Override 125 | public ListIterator listIterator() { 126 | return new ListIterator<>() { 127 | @Override 128 | public boolean hasNext() { 129 | return false; 130 | } 131 | 132 | @Override 133 | public Integer next() { 134 | return null; 135 | } 136 | 137 | @Override 138 | public boolean hasPrevious() { 139 | return false; 140 | } 141 | 142 | @Override 143 | public Integer previous() { 144 | return null; 145 | } 146 | 147 | @Override 148 | public int nextIndex() { 149 | return 0; 150 | } 151 | 152 | @Override 153 | public int previousIndex() { 154 | return 0; 155 | } 156 | 157 | @Override 158 | public void remove() { 159 | 160 | } 161 | 162 | @Override 163 | public void set(Integer integer) { 164 | 165 | } 166 | 167 | @Override 168 | public void add(Integer integer) { 169 | 170 | } 171 | }; 172 | } 173 | 174 | @Override 175 | public ListIterator listIterator(int index) { 176 | return new ListIterator<>() { 177 | @Override 178 | public boolean hasNext() { 179 | return false; 180 | } 181 | 182 | @Override 183 | public Integer next() { 184 | return null; 185 | } 186 | 187 | @Override 188 | public boolean hasPrevious() { 189 | return false; 190 | } 191 | 192 | @Override 193 | public Integer previous() { 194 | return null; 195 | } 196 | 197 | @Override 198 | public int nextIndex() { 199 | return 0; 200 | } 201 | 202 | @Override 203 | public int previousIndex() { 204 | return 0; 205 | } 206 | 207 | @Override 208 | public void remove() { 209 | 210 | } 211 | 212 | @Override 213 | public void set(Integer integer) { 214 | 215 | } 216 | 217 | @Override 218 | public void add(Integer integer) { 219 | 220 | } 221 | }; 222 | } 223 | 224 | @Override 225 | public List subList(int fromIndex, int toIndex) { 226 | return this; 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /src/test/java/com/kousenit/wikipedia/BioServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.wikipedia; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.mockito.AdditionalAnswers; 5 | import org.mockito.MockedStatic; 6 | 7 | import java.util.Arrays; 8 | import java.util.List; 9 | 10 | import static org.assertj.core.api.Assertions.assertThat; 11 | import static org.junit.jupiter.api.Assertions.assertEquals; 12 | import static org.mockito.ArgumentMatchers.anyString; 13 | import static org.mockito.Mockito.mockStatic; 14 | import static org.mockito.Mockito.times; 15 | 16 | class BioServiceTest { 17 | @Test // Integration test 18 | void checkBios() { 19 | BioService service = new BioService("Anita Borg", "Ada Lovelace", 20 | "Grace Hopper", "Barbara Liskov"); 21 | List bios = service.getBios(); 22 | assertEquals(4, bios.size()); 23 | bios.forEach(bio -> { 24 | // System.out.println(bio); 25 | String[] strings = bio.split("="); 26 | String[] bioStrings = strings[1].split("\\n"); 27 | System.out.println("Title: " + strings[0].substring(1)); 28 | Arrays.stream(bioStrings) 29 | .forEach(System.out::println); 30 | System.out.println("-------------------"); 31 | }); 32 | } 33 | 34 | @Test 35 | void testBioServiceWithMocks() { 36 | BioService service = new BioService( 37 | "Anita Borg", "Ada Lovelace", "Grace Hopper", "Barbara Liskov"); 38 | try (MockedStatic mocked = mockStatic(WikiUtil.class)) { 39 | mocked.when(() -> WikiUtil.getWikipediaExtract(anyString())) 40 | .thenAnswer(AdditionalAnswers.returnsFirstArg()); 41 | assertThat(service.getBios()).hasSize(4); 42 | mocked.verify(() -> WikiUtil.getWikipediaExtract(anyString()), times(4)); 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /src/test/java/com/kousenit/wikipedia/WikiPageTest.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.wikipedia; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.*; 6 | 7 | class WikiPageTest { 8 | 9 | @Test 10 | void testGettersAndSetters() { 11 | WikiPage page = new WikiPage(); 12 | 13 | // Test pageId 14 | page.setPageid(123); 15 | assertEquals(123, page.getPageid()); 16 | 17 | // Test namespace 18 | page.setNs(0); 19 | assertEquals(0, page.getNs()); 20 | 21 | // Test title 22 | page.setTitle("Grace Hopper"); 23 | assertEquals("Grace Hopper", page.getTitle()); 24 | 25 | // Test extract 26 | String extract = "Grace Brewster Hopper was an American computer scientist, " + 27 | "mathematician, and United States Navy rear admiral."; 28 | page.setExtract(extract); 29 | assertEquals(extract, page.getExtract()); 30 | 31 | // Test missing flag 32 | page.setMissing(true); 33 | assertTrue(page.getMissing()); 34 | 35 | page.setMissing(false); 36 | assertFalse(page.getMissing()); 37 | } 38 | 39 | @Test 40 | void testToString() { 41 | WikiPage page = new WikiPage(); 42 | page.setPageid(123); 43 | page.setNs(0); 44 | page.setTitle("Ada Lovelace"); 45 | page.setExtract("Ada Lovelace was an English mathematician and writer."); 46 | page.setMissing(false); 47 | 48 | String expected = "WikiPage{" + 49 | "pageid=123" + 50 | ", ns=0" + 51 | ", title='Ada Lovelace'" + 52 | ", extract='Ada Lovelace was an English mathematician and writer.'" + 53 | ", missing=false" + 54 | '}'; 55 | 56 | assertEquals(expected, page.toString()); 57 | } 58 | } -------------------------------------------------------------------------------- /src/test/java/com/kousenit/wikipedia/WikiResponseTest.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.wikipedia; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | import static org.junit.jupiter.api.Assertions.*; 9 | 10 | class WikiResponseTest { 11 | 12 | @Test 13 | void testGettersAndSetters() { 14 | WikiResponse response = new WikiResponse(); 15 | 16 | // Test batchcomplete 17 | response.setBatchcomplete("true"); 18 | assertEquals("true", response.getBatchcomplete()); 19 | 20 | // Test query and pages 21 | List pages = new ArrayList<>(); 22 | WikiPage page = new WikiPage(); 23 | page.setTitle("Ada Lovelace"); 24 | page.setExtract("Ada Lovelace was an English mathematician and writer."); 25 | pages.add(page); 26 | 27 | WikiQuery query = new WikiQuery(); 28 | query.setPages(pages); 29 | 30 | response.setQuery(query); 31 | assertEquals(query, response.getQuery()); 32 | assertEquals(pages, response.getQuery().getPages()); 33 | } 34 | 35 | @Test 36 | void testToString() { 37 | WikiResponse response = new WikiResponse(); 38 | response.setBatchcomplete("true"); 39 | 40 | List pages = new ArrayList<>(); 41 | WikiPage page = new WikiPage(); 42 | page.setTitle("Ada Lovelace"); 43 | page.setExtract("Ada Lovelace was an English mathematician and writer."); 44 | pages.add(page); 45 | 46 | WikiQuery query = new WikiQuery(); 47 | query.setPages(pages); 48 | 49 | response.setQuery(query); 50 | 51 | String result = response.toString(); 52 | assertNotNull(result); 53 | assertTrue(result.contains("batchcomplete=true")); 54 | } 55 | } -------------------------------------------------------------------------------- /src/test/java/com/kousenit/wikipedia/WikiUtilTest.java: -------------------------------------------------------------------------------- 1 | package com.kousenit.wikipedia; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.assertj.core.api.Assertions.assertThat; 6 | import static org.assertj.core.api.Assertions.assertThatExceptionOfType; 7 | 8 | class WikiUtilTest { 9 | 10 | @Test 11 | void getStringResponse() { 12 | String graceHopper = WikiUtil.getWikipediaExtract("Grace Hopper"); 13 | System.out.println(graceHopper); 14 | assertThat(graceHopper).contains("Grace", "Hopper"); 15 | } 16 | 17 | @Test 18 | void getResponseNotFound() { 19 | assertThatExceptionOfType(RuntimeException.class) 20 | .isThrownBy(() -> WikiUtil.getWikipediaExtract("Not a real page")) 21 | .withMessageContaining("Page not found"); 22 | } 23 | } -------------------------------------------------------------------------------- /src/test/resources/junit-platform.properties: -------------------------------------------------------------------------------- 1 | junit.platform.output.capture.stdout=true 2 | junit.platform.output.capture.stderr=true 3 | -------------------------------------------------------------------------------- /src/test/resources/mappings/response.json: -------------------------------------------------------------------------------- 1 | { 2 | "people": [ 3 | { 4 | "craft": "Discovery One", 5 | "name": "David Bowman" 6 | }, 7 | { 8 | "craft": "Discovery One", 9 | "name": "Frank Poole" 10 | }, 11 | { 12 | "craft": "Discovery One", 13 | "name": "HAL 9000" 14 | } 15 | ], 16 | "message": "success", 17 | "number": 3 18 | } --------------------------------------------------------------------------------