diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 8609981c54..5a49c782b4 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -54,7 +54,7 @@ Things we pay attention in a PR : * In the code, always test your feature / change, in unit tests and in our `acceptance test suite` located in `org.mockitousage`. Older tests will be migrated when a test is modified. * New test methods should follow a snake case convention (`ensure_that_stuff_is_doing_that`), this allows the test name to be fully expressive on intent while still readable. * When reporting errors to the users, if it's a user report report it gently and explain how a user should deal with it, see the `Reporter` class. However not all errors should go there, some unlikely technical errors don't need to be in the `Reporter` class. -* Documentation !!! Always document with love the public API. Internals could use some love too. In all cases the code should _auto-document_ itself like any [well designed API](rebased and squashed if necessary, so that each commit clearly changes one things and there are no extraneous fix-ups). +* Documentation !!! Always document with love the public API. Internals could use some love too. In all cases the code should _auto-document_ itself like any well-designed API. * We use (4) spaces instead of tabs. Make sure line ending is Unix style (LF). More on line ending on the [Github help](https://help.github.com/articles/dealing-with-line-endings/). diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7a6d99b687..1bef031588 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,6 +14,9 @@ on: pull_request: branches: ['**'] +permissions: + contents: read + jobs: # @@ -26,8 +29,13 @@ jobs: # Definition of the build matrix strategy: matrix: - java: [8, 11, 17] - mock-maker: ['mock-maker-default', 'mock-maker-inline'] + java: [11, 17] + entry: + - { mock-maker: 'mock-maker-default', member-accessor: 'member-accessor-default' } + - { mock-maker: 'mock-maker-inline', member-accessor: 'member-accessor-module' } + - { mock-maker: 'mock-maker-subclass', member-accessor: 'member-accessor-module' } + - { mock-maker: 'mock-maker-subclass', member-accessor: 'member-accessor-reflection' } + - { mock-maker: 'mock-maker-inline', member-accessor: 'member-accessor-reflection' } # All build steps # SINGLE-MATRIX-JOB means that the step does not need to be executed on every job in the matrix @@ -45,21 +53,22 @@ jobs: java-version: ${{ matrix.java }} - name: 3. Validate Gradle wrapper - if: matrix.java == 8 && matrix.mock-maker == 'mock-maker-default' # SINGLE-MATRIX-JOB - uses: gradle/wrapper-validation-action@v1.0.4 # https://github.com/gradle/wrapper-validation-action + if: matrix.java == 11 && matrix.entry.mock-maker == 'mock-maker-default' # SINGLE-MATRIX-JOB + uses: gradle/wrapper-validation-action@v1.0.6 # https://github.com/gradle/wrapper-validation-action - name: 4. Build and check reproducibility of artifacts (single job only) - if: matrix.java == 8 && matrix.mock-maker == 'mock-maker-default' # SINGLE-MATRIX-JOB + if: matrix.java == 11 && matrix.entry.mock-maker == 'mock-maker-default' # SINGLE-MATRIX-JOB run: ./check_reproducibility.sh - name: 5. Spotless check (single job only). Run './gradlew spotlessApply' locally if this job fails. - if: matrix.java == 11 && matrix.mock-maker == 'mock-maker-default' # SINGLE-MATRIX-JOB + if: matrix.java == 11 && matrix.entry.mock-maker == 'mock-maker-default' # SINGLE-MATRIX-JOB run: ./gradlew spotlessCheck - - name: 6. Build on Java ${{ matrix.java }} with ${{ matrix.mock-maker }} + - name: 6. Build on Java ${{ matrix.java }} with ${{ matrix.entry.mock-maker }} and ${{ matrix.entry.member-accessor }} run: ./gradlew build idea --scan env: - MOCK_MAKER: ${{ matrix.mock-maker }} + MOCK_MAKER: ${{ matrix.entry.mock-maker }} + MEMBER_ACCESSOR: ${{ matrix.entry.member-accessor }} - name: 7. Upload coverage report run: | @@ -76,11 +85,93 @@ jobs: chmod +x codecov ./codecov + # + # Android build job + # + android: + runs-on: macos-latest + if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')" + timeout-minutes: 30 + + # Definition of the build matrix + strategy: + matrix: + # Minimum supported and maximum available. + android-api: [ 26, 33 ] + + # All build steps + steps: + - name: 1. Check out code + uses: actions/checkout@v3 + with: + fetch-depth: '0' # https://github.com/shipkit/shipkit-changelog#fetch-depth-on-ci + + - name: 2. Set up Java 11 + uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: 11 + + - name: 3. Run Android tests on Android API level ${{ matrix.android-api }} + uses: reactivecircus/android-emulator-runner@v2 + with: + arch: x86_64 + api-level: ${{ matrix.android-api }} + # Workaround for https://issuetracker.google.com/issues/267458959 + target: ${{ matrix.android-api >= 32 && 'google_apis' || 'default' }} + # Workaround for https://github.com/ReactiveCircus/android-emulator-runner/issues/160, but newer version. + emulator-build: 9322596 # 31.3.14.0 got it via `emulator -version` + # Override default "-no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim" + # See emulator manual for reference: https://developer.android.com/studio/run/emulator-commandline + emulator-options: > + -no-window + -gpu swiftshader_indirect + -no-snapshot + -noaudio + -no-boot-anim + -camera-back none + -camera-front none + # See logcat manual for reference: https://developer.android.com/studio/command-line/logcat + script: | + # Capture logcat output from "Launch Emulator" to a file. + adb logcat -d > emulator-startup.log + # Shorten the logcat output, by truncating at this point, the relevant part is yet to come. + # Best effort, could fail with "failed to clear the 'main' log", + # because something is locking logcat, so try a few times, and ignore errors each time. + adb logcat --clear || true + adb logcat --clear || true + adb logcat --clear || true + # Capture full logcat output to a file. + adb logcat > emulator.log & echo $! > logcat_file.pid + # Output instrumentation test logs to the GitHub Actions output. + adb logcat "*:S MonitoringInstr:V AndroidJUnitRunner:V TestRequestBuilder:V TestExecutor:V TestRunner:V" --format=color & echo $! > logcat_console.pid + + echo 0 > gradle.exit # Set a default exit code. + # Run the actual tests (suppress build failures by saving the exit code). + ./gradlew :androidTest:connectedCheck --no-daemon --no-build-cache || echo $? > gradle.exit + + # Stop capturing logcat output. + kill $(cat logcat_file.pid) || echo "::warning file=.github/workflows/ci.yml::Logcat process $(cat logcat_file.pid) didn't exist." + kill $(cat logcat_console.pid) || echo "::warning file=.github/workflows/ci.yml::Logcat process $(cat logcat_console.pid) didn't exist." + # Make sure the step fails if the tests failed. + exit $(cat gradle.exit) + + - name: 4. Upload artifact "androidTest-results-${{ matrix.android-api }}" + if: success() || failure() + uses: actions/upload-artifact@v3 + with: + name: androidTest-results-${{ matrix.android-api }} + path: | + ${{ github.workspace }}/subprojects/androidTest/build/reports/androidTests/connected/** + ${{ github.workspace }}/emulator.log + ${{ github.workspace }}/emulator-startup.log # # Release job, only for pushes to the main development branch # release: + permissions: + contents: write runs-on: ubuntu-latest needs: [build] # build job must pass before we can release diff --git a/.gitignore b/.gitignore index 5267dfa417..17410c4d48 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,9 @@ .settings/ bin/ +# VSCode +.vscode/ + # Gradle & builds build build-cache @@ -26,4 +29,4 @@ classes *.log # Used to check reproducibility of Mockito jars -checksums/ \ No newline at end of file +checksums/ diff --git a/README.md b/README.md index 0e36847557..0a34afc47b 100644 --- a/README.md +++ b/README.md @@ -10,19 +10,21 @@ Most popular mocking framework for Java [![Coverage Status](https://img.shields.io/codecov/c/github/mockito/mockito.svg)](https://codecov.io/github/mockito/mockito) [![MIT License](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/mockito/mockito/blob/main/LICENSE) -[![Release Notes](https://img.shields.io/badge/release%20notes-3.x-yellow.svg)](https://github.com/mockito/mockito/releases/) +[![Release Notes](https://img.shields.io/badge/release%20notes-5.x-yellow.svg)](https://github.com/mockito/mockito/releases/) [![Maven Central](https://img.shields.io/maven-central/v/org.mockito/mockito-core.svg)](https://search.maven.org/artifact/org.mockito/mockito-core/) [![Javadoc](https://www.javadoc.io/badge/org.mockito/mockito-core.svg)](https://www.javadoc.io/doc/org.mockito/mockito-core) -## Current version is 4.x -Still on Mockito 1.x? See [what's new](https://github.com/mockito/mockito/wiki/What%27s-new-in-Mockito-2) in Mockito 2! [Mockito 3](https://github.com/mockito/mockito/releases/tag/v3.0.0) does not introduce any breaking API changes, but now requires Java 8 over Java 6 for Mockito 2. +## Current version is 5.x +Still on Mockito 1.x? See [what's new](https://github.com/mockito/mockito/wiki/What%27s-new-in-Mockito-2) in Mockito 2! +[Mockito 3](https://github.com/mockito/mockito/releases/tag/v3.0.0) does not introduce any breaking API changes, but now requires Java 8 over Java 6 for Mockito 2. [Mockito 4](https://github.com/mockito/mockito/releases/tag/v4.0.0) removes deprecated API. +[Mockito 5](https://github.com/mockito/mockito/releases/tag/v5.0.0) switches the default mockmaker to mockito-inline, and now requires Java 11. ## Mockito for enterprise -Available as part of the Tidelift Subscription +Available as part of the [Tidelift](https://tidelift.com/subscription/pkg/maven-org-mockito-mockito-core) Subscription. The maintainers of org.mockito:mockito-core and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, @@ -73,7 +75,7 @@ Then, _open_ the generated *.ipr file in IDEA. ## How to release new version? -1. Every change on the main development branch is released as -SNAPSHOT version to Sonatype snapshot repo +1. Every change on the main development branch is released as `-SNAPSHOT` version to Sonatype snapshot repo at https://s01.oss.sonatype.org/content/repositories/snapshots/org/mockito/mockito-core. 2. In order to release a non-snapshot version to Maven Central push an annotated tag, for example: diff --git a/build.gradle b/build.gradle index f3f9a1dd26..de764459c5 100644 --- a/build.gradle +++ b/build.gradle @@ -7,24 +7,24 @@ buildscript { dependencies { classpath 'gradle.plugin.com.hierynomus.gradle.plugins:license-gradle-plugin:0.16.1' - classpath 'net.ltgt.gradle:gradle-errorprone-plugin:2.0.2' + classpath 'net.ltgt.gradle:gradle-errorprone-plugin:3.0.1' - classpath "io.github.gradle-nexus:publish-plugin:1.1.0" + classpath "io.github.gradle-nexus:publish-plugin:1.3.0" classpath 'org.shipkit:shipkit-changelog:1.2.0' - classpath 'org.shipkit:shipkit-auto-version:1.2.1' + classpath 'org.shipkit:shipkit-auto-version:1.2.2' - classpath 'com.google.googlejavaformat:google-java-format:1.15.0' - classpath 'com.android.tools.build:gradle:4.2.0' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10" + classpath 'com.google.googlejavaformat:google-java-format:1.16.0' + classpath 'com.android.tools.build:gradle:7.3.1' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.10" } } plugins { - id 'com.diffplug.spotless' version '6.9.1' + id 'com.diffplug.spotless' version '6.18.0' id 'eclipse' - id 'com.github.ben-manes.versions' version '0.42.0' - id 'biz.aQute.bnd.builder' version '6.3.1' - id 'ru.vyarus.animalsniffer' version '1.5.2' + id 'com.github.ben-manes.versions' version '0.46.0' + id 'biz.aQute.bnd.builder' version '6.4.0' + id 'ru.vyarus.animalsniffer' version '1.7.0' } description = 'Mockito mock objects library core API and implementation' @@ -52,10 +52,8 @@ allprojects { proj -> mavenCentral() google() } - if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_11)) { - plugins.withId('java') { - proj.apply from: "$rootDir/gradle/errorprone.gradle" - } + plugins.withId('java') { + proj.apply from: "$rootDir/gradle/errorprone.gradle" } tasks.withType(JavaCompile) { //I don't believe those warnings add value given modern IDEs @@ -66,7 +64,7 @@ allprojects { proj -> options.addStringOption('Xdoclint:none', '-quiet') options.addStringOption('encoding', 'UTF-8') options.addStringOption('charSet', 'UTF-8') - options.setSource('8') + options.setSource('11') } tasks.withType(AbstractArchiveTask) { @@ -84,6 +82,9 @@ allprojects { proj -> configurations { testUtil //TODO move to separate project + // Putting 'provided' dependencies on test compile and runtime classpath. + testCompileOnly.extendsFrom(compileOnly) + testRuntimeOnly.extendsFrom(compileOnly) } dependencies { @@ -94,19 +95,24 @@ dependencies { testImplementation libraries.assertj - //putting 'provided' dependencies on test compile and runtime classpath - testCompileOnly configurations.compileOnly - testRuntimeOnly configurations.compileOnly - testUtil sourceSets.test.output signature 'org.codehaus.mojo.signature:java18:1.0@signature' - signature 'net.sf.androidscents.signature:android-api-level-24:7.0_r2@signature' + signature 'net.sf.androidscents.signature:android-api-level-26:8.0.0_r2@signature' } animalsniffer { sourceSets = [sourceSets.main] annotation = 'org.mockito.internal.SuppressSignatureCheck' + // See please https://github.com/mojohaus/animal-sniffer/issues/172 + ignore += [ + 'java.lang.instrument.Instrumentation', + 'java.lang.invoke.MethodHandle', + 'java.lang.invoke.MethodHandles$Lookup', + 'java.lang.StackWalker', + 'java.lang.StackWalker$StackFrame', + 'java.lang.StackWalker$Option' + ] } spotless { @@ -117,9 +123,10 @@ spotless { licenseHeaderFile rootProject.file('config/spotless/spotless.header') custom 'google-java-format', { source -> - com.google.googlejavaformat.java.JavaFormatterOptions options = new com.google.googlejavaformat.java.JavaFormatterOptions.Builder() + com.google.googlejavaformat.java.JavaFormatterOptions options = new com.google.googlejavaformat.java.AutoValue_JavaFormatterOptions.Builder() .style(com.google.googlejavaformat.java.JavaFormatterOptions.Style.AOSP) .formatJavadoc(false) + .reorderModifiers(true) .build() com.google.googlejavaformat.java.Formatter formatter = new com.google.googlejavaformat.java.Formatter(options) return formatter.formatSource(source) diff --git a/gradle.properties b/gradle.properties index 3ccf4c9a66..ad11ad8da0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,6 +4,7 @@ org.gradle.caching=true org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 \ -XX:+IgnoreUnrecognizedVMOptions \ --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \ + --add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED \ --add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED \ --add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED \ --add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED \ diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index aebe3791cb..d97e022c15 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -4,16 +4,16 @@ ext { def versions = [:] -versions.bytebuddy = '1.12.13' -versions.junitJupiter = '5.9.0' -versions.errorprone = '2.14.0' +versions.bytebuddy = '1.14.4' +versions.junitJupiter = '5.9.2' +versions.errorprone = '2.18.0' libraries.junit4 = 'junit:junit:4.13.2' libraries.junitJupiterApi = "org.junit.jupiter:junit-jupiter-api:${versions.junitJupiter}" -libraries.junitPlatformLauncher = 'org.junit.platform:junit-platform-launcher:1.9.0' +libraries.junitPlatformLauncher = 'org.junit.platform:junit-platform-launcher:1.9.2' libraries.junitJupiterEngine = "org.junit.jupiter:junit-jupiter-engine:${versions.junitJupiter}" libraries.junitVintageEngine = "org.junit.vintage:junit-vintage-engine:${versions.junitJupiter}" -libraries.assertj = 'org.assertj:assertj-core:3.23.1' +libraries.assertj = 'org.assertj:assertj-core:3.24.2' libraries.hamcrest = 'org.hamcrest:hamcrest-core:2.2' libraries.opentest4j = 'org.opentest4j:opentest4j:1.2.0' @@ -26,15 +26,15 @@ libraries.errorproneTestApi = "com.google.errorprone:error_prone_test_helpers:${ libraries.autoservice = "com.google.auto.service:auto-service:1.0.1" -libraries.objenesis = 'org.objenesis:objenesis:3.2' +libraries.objenesis = 'org.objenesis:objenesis:3.3' libraries.osgi = 'org.osgi:osgi.core:8.0.0' -libraries.equinox = 'org.eclipse.platform:org.eclipse.osgi:3.18.0' -libraries.bndGradle = 'biz.aQute.bnd:biz.aQute.bnd.gradle:6.3.1' +libraries.equinox = 'org.eclipse.platform:org.eclipse.osgi:3.18.300' +libraries.bndGradle = 'biz.aQute.bnd:biz.aQute.bnd.gradle:6.4.0' -libraries.groovy = 'org.codehaus.groovy:groovy:3.0.12' +libraries.groovy = 'org.codehaus.groovy:groovy:3.0.17' -def kotlinVersion = '1.7.10' +def kotlinVersion = '1.8.10' libraries.kotlin = [ version: kotlinVersion, @@ -43,9 +43,6 @@ libraries.kotlin = [ gradlePlugin: "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}", ] libraries.android = [ - ktx: 'androidx.core:core-ktx:1.8.0', - compat: 'androidx.appcompat:appcompat:1.4.2', - material: 'com.google.android.material:material:1.6.1', - junit: 'androidx.test.ext:junit:1.1.3', - espresso: 'androidx.test.espresso:espresso-core:3.4.0', + runner: 'androidx.test:runner:1.5.2', + junit: 'androidx.test.ext:junit:1.1.5', ] diff --git a/gradle/errorprone.gradle b/gradle/errorprone.gradle index 1d9cf7428e..d7d551432f 100644 --- a/gradle/errorprone.gradle +++ b/gradle/errorprone.gradle @@ -1,12 +1,6 @@ apply plugin: "net.ltgt.errorprone" -if (JavaVersion.current() == JavaVersion.VERSION_1_8) { - dependencies { - errorproneJavac("com.google.errorprone:javac:9+181-r4173-1") - } -} - dependencies { errorprone libraries.errorprone } diff --git a/gradle/java-library.gradle b/gradle/java-library.gradle index b5949b76fe..d6a9c175b8 100644 --- a/gradle/java-library.gradle +++ b/gradle/java-library.gradle @@ -14,8 +14,8 @@ generatePomFileForJavaLibraryPublication.doLast { assert pom.name == archivesBaseName } -sourceCompatibility = 1.8 -targetCompatibility = 1.8 +sourceCompatibility = 11 +targetCompatibility = 11 test { include "**/*Test.class" @@ -30,8 +30,8 @@ apply from: "$rootDir/gradle/retry-test.gradle" tasks.withType(Checkstyle) { reports { - xml.enabled false - html.enabled true + xml.required = false + html.required = true html.stylesheet resources.text.fromFile("$rootDir/config/checkstyle/checkstyle.xsl") } } diff --git a/gradle/java-publication.gradle b/gradle/java-publication.gradle index 8c0b255d7e..4fe0103dfb 100644 --- a/gradle/java-publication.gradle +++ b/gradle/java-publication.gradle @@ -58,7 +58,7 @@ publishing { //Gradle does not write 'jar' packaging to the pom (unlike other packaging types). //This is OK because 'jar' is implicit/default: // https://maven.apache.org/guides/introduction/introduction-to-the-pom.html#minimal-pom - packaging = project.tasks.jar.extension + packaging = project.tasks.jar.archiveExtension.get() } url = "https://github.com/mockito/mockito" diff --git a/gradle/mockito-core/inline-mock.gradle b/gradle/mockito-core/inline-mock.gradle index d555ad6f09..70b5ed86f9 100644 --- a/gradle/mockito-core/inline-mock.gradle +++ b/gradle/mockito-core/inline-mock.gradle @@ -1,24 +1,19 @@ -task copyMockMethodDispatcher { +tasks.register('copyMockMethodDispatcher', Copy) { dependsOn compileJava - outputs.files files("${sourceSets.main.java.outputDir}/org/mockito/internal/creation/bytebuddy/inject") - .asFileTree - .matching { include "*.raw" } - .files - .collect { "${buildDir}/${it.name}" } - doLast { - copy { - from "${sourceSets.main.java.outputDir}/org/mockito/internal/creation/bytebuddy/inject" - into "${sourceSets.main.java.outputDir}/org/mockito/internal/creation/bytebuddy/inject" - include 'MockMethodDispatcher.class' - rename { String fileName -> - fileName.replace('.class', '.raw') - } - } - } + from "${sourceSets.main.java.classesDirectory.get()}/org/mockito/internal/creation/bytebuddy/inject/MockMethodDispatcher.class" + into layout.buildDirectory.dir("generated/resources/inline/org/mockito/internal/creation/bytebuddy/inject") + + rename '(.+)\\.class', '$1.raw' } classes.dependsOn(copyMockMethodDispatcher) +sourceSets.main { + resources { + output.dir(layout.buildDirectory.dir("generated/resources/inline")) + } +} + jar { exclude("org/mockito/internal/creation/bytebuddy/inject/package-info.class") exclude("org/mockito/internal/creation/bytebuddy/inject/MockMethodDispatcher.class") diff --git a/gradle/mockito-core/java-docs/element-list b/gradle/mockito-core/java-docs/element-list new file mode 100644 index 0000000000..4cfabf8fdf --- /dev/null +++ b/gradle/mockito-core/java-docs/element-list @@ -0,0 +1,282 @@ +module:java.base +java.io +java.lang +java.lang.annotation +java.lang.invoke +java.lang.module +java.lang.ref +java.lang.reflect +java.math +java.net +java.net.spi +java.nio +java.nio.channels +java.nio.channels.spi +java.nio.charset +java.nio.charset.spi +java.nio.file +java.nio.file.attribute +java.nio.file.spi +java.security +java.security.acl +java.security.cert +java.security.interfaces +java.security.spec +java.text +java.text.spi +java.time +java.time.chrono +java.time.format +java.time.temporal +java.time.zone +java.util +java.util.concurrent +java.util.concurrent.atomic +java.util.concurrent.locks +java.util.function +java.util.jar +java.util.regex +java.util.spi +java.util.stream +java.util.zip +javax.crypto +javax.crypto.interfaces +javax.crypto.spec +javax.net +javax.net.ssl +javax.security.auth +javax.security.auth.callback +javax.security.auth.login +javax.security.auth.spi +javax.security.auth.x500 +javax.security.cert +module:java.compiler +javax.annotation.processing +javax.lang.model +javax.lang.model.element +javax.lang.model.type +javax.lang.model.util +javax.tools +module:java.datatransfer +java.awt.datatransfer +module:java.desktop +java.applet +java.awt +java.awt.color +java.awt.desktop +java.awt.dnd +java.awt.event +java.awt.font +java.awt.geom +java.awt.im +java.awt.im.spi +java.awt.image +java.awt.image.renderable +java.awt.print +java.beans +java.beans.beancontext +javax.accessibility +javax.imageio +javax.imageio.event +javax.imageio.metadata +javax.imageio.plugins.bmp +javax.imageio.plugins.jpeg +javax.imageio.plugins.tiff +javax.imageio.spi +javax.imageio.stream +javax.print +javax.print.attribute +javax.print.attribute.standard +javax.print.event +javax.sound.midi +javax.sound.midi.spi +javax.sound.sampled +javax.sound.sampled.spi +javax.swing +javax.swing.border +javax.swing.colorchooser +javax.swing.event +javax.swing.filechooser +javax.swing.plaf +javax.swing.plaf.basic +javax.swing.plaf.metal +javax.swing.plaf.multi +javax.swing.plaf.nimbus +javax.swing.plaf.synth +javax.swing.table +javax.swing.text +javax.swing.text.html +javax.swing.text.html.parser +javax.swing.text.rtf +javax.swing.tree +javax.swing.undo +module:java.instrument +java.lang.instrument +module:java.logging +java.util.logging +module:java.management +java.lang.management +javax.management +javax.management.loading +javax.management.modelmbean +javax.management.monitor +javax.management.openmbean +javax.management.relation +javax.management.remote +javax.management.timer +module:java.management.rmi +javax.management.remote.rmi +module:java.naming +javax.naming +javax.naming.directory +javax.naming.event +javax.naming.ldap +javax.naming.spi +module:java.net.http +java.net.http +module:java.prefs +java.util.prefs +module:java.rmi +java.rmi +java.rmi.activation +java.rmi.dgc +java.rmi.registry +java.rmi.server +javax.rmi.ssl +module:java.scripting +javax.script +module:java.se +module:java.security.jgss +javax.security.auth.kerberos +org.ietf.jgss +module:java.security.sasl +javax.security.sasl +module:java.smartcardio +javax.smartcardio +module:java.sql +java.sql +javax.sql +module:java.sql.rowset +javax.sql.rowset +javax.sql.rowset.serial +javax.sql.rowset.spi +module:java.transaction.xa +javax.transaction.xa +module:java.xml +javax.xml +javax.xml.catalog +javax.xml.datatype +javax.xml.namespace +javax.xml.parsers +javax.xml.stream +javax.xml.stream.events +javax.xml.stream.util +javax.xml.transform +javax.xml.transform.dom +javax.xml.transform.sax +javax.xml.transform.stax +javax.xml.transform.stream +javax.xml.validation +javax.xml.xpath +org.w3c.dom +org.w3c.dom.bootstrap +org.w3c.dom.events +org.w3c.dom.ls +org.w3c.dom.ranges +org.w3c.dom.traversal +org.w3c.dom.views +org.xml.sax +org.xml.sax.ext +org.xml.sax.helpers +module:java.xml.crypto +javax.xml.crypto +javax.xml.crypto.dom +javax.xml.crypto.dsig +javax.xml.crypto.dsig.dom +javax.xml.crypto.dsig.keyinfo +javax.xml.crypto.dsig.spec +module:jdk.accessibility +com.sun.java.accessibility.util +module:jdk.attach +com.sun.tools.attach +com.sun.tools.attach.spi +module:jdk.charsets +module:jdk.compiler +com.sun.source.doctree +com.sun.source.tree +com.sun.source.util +com.sun.tools.javac +module:jdk.crypto.cryptoki +module:jdk.crypto.ec +module:jdk.dynalink +jdk.dynalink +jdk.dynalink.beans +jdk.dynalink.linker +jdk.dynalink.linker.support +jdk.dynalink.support +module:jdk.editpad +module:jdk.hotspot.agent +module:jdk.httpserver +com.sun.net.httpserver +com.sun.net.httpserver.spi +module:jdk.jartool +com.sun.jarsigner +jdk.security.jarsigner +module:jdk.javadoc +com.sun.javadoc +com.sun.tools.javadoc +jdk.javadoc.doclet +module:jdk.jcmd +module:jdk.jconsole +com.sun.tools.jconsole +module:jdk.jdeps +module:jdk.jdi +com.sun.jdi +com.sun.jdi.connect +com.sun.jdi.connect.spi +com.sun.jdi.event +com.sun.jdi.request +module:jdk.jdwp.agent +module:jdk.jfr +jdk.jfr +jdk.jfr.consumer +module:jdk.jlink +module:jdk.jshell +jdk.jshell +jdk.jshell.execution +jdk.jshell.spi +jdk.jshell.tool +module:jdk.jsobject +netscape.javascript +module:jdk.jstatd +module:jdk.localedata +module:jdk.management +com.sun.management +module:jdk.management.agent +module:jdk.management.jfr +jdk.management.jfr +module:jdk.naming.dns +module:jdk.naming.rmi +module:jdk.net +jdk.net +jdk.nio +module:jdk.pack +module:jdk.rmic +module:jdk.scripting.nashorn +jdk.nashorn.api.scripting +jdk.nashorn.api.tree +module:jdk.sctp +com.sun.nio.sctp +module:jdk.security.auth +com.sun.security.auth +com.sun.security.auth.callback +com.sun.security.auth.login +com.sun.security.auth.module +module:jdk.security.jgss +com.sun.security.jgss +module:jdk.xml.dom +org.w3c.dom.css +org.w3c.dom.html +org.w3c.dom.stylesheets +org.w3c.dom.xpath +module:jdk.zipfs diff --git a/gradle/mockito-core/javadoc.gradle b/gradle/mockito-core/javadoc.gradle index 3d42d21e1f..9a995ec45d 100644 --- a/gradle/mockito-core/javadoc.gradle +++ b/gradle/mockito-core/javadoc.gradle @@ -1,5 +1,7 @@ //It seems the gradle javadoc task works file by file and as such disable some features of javadoc tool //such as link to packages, https://groups.google.com/d/msg/gradle-dev/R83dy_6PHMc/bgw0cUTMFAAJ +def javaDocsDir = 'gradle/mockito-core/java-docs' + javadoc { description "Creates javadoc html for Mockito API." @@ -38,8 +40,8 @@ javadoc { options.noIndex = false options.noNavBar = false options.noTree = false - options.links = ['https://docs.oracle.com/javase/6/docs/api/', - 'https://junit.org/junit4/javadoc/4.12/'] + options.links('https://junit.org/junit4/javadoc/4.13.2/') + options.linksOffline('https://docs.oracle.com/en/java/javase/11/docs/api/', javaDocsDir) options.bottom(""" diff --git a/gradle/mockito-core/osgi.gradle b/gradle/mockito-core/osgi.gradle index 8dbf3e9063..f950f98de2 100644 --- a/gradle/mockito-core/osgi.gradle +++ b/gradle/mockito-core/osgi.gradle @@ -8,7 +8,7 @@ jar { 'Bundle-SymbolicName': 'org.mockito.mockito-core', 'Bundle-Version': "\${version_cleanup;${project.version}}", '-versionpolicy': '[${version;==;${@}},${version;+;${@}})', - 'Export-Package': "org.mockito.internal.*;status=INTERNAL;mandatory:=status;version=${version},org.mockito.*;version=${version}", + 'Export-Package': "org.mockito.internal.*;status=INTERNAL;mandatory:=status;version=${archiveVersion.get()},org.mockito.*;version=${archiveVersion.get()}", 'Import-Package': [ 'net.bytebuddy.*;version="[1.6.0,2.0)"', 'junit.*;resolution:=optional', diff --git a/gradle/mockito-core/testing.gradle b/gradle/mockito-core/testing.gradle index a7c14c1f62..79e2996b6c 100644 --- a/gradle/mockito-core/testing.gradle +++ b/gradle/mockito-core/testing.gradle @@ -6,24 +6,43 @@ if (java11 != null) { } } -task(createTestResources).doLast { - def mockMakerFile = new File("$projectDir/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker") - if (System.env.MOCK_MAKER != null) { - logger.info("Using MockMaker ${System.env.MOCK_MAKER}") - mockMakerFile.parentFile.mkdirs() - mockMakerFile.createNewFile() - mockMakerFile.write(System.env.MOCK_MAKER) - } else { - logger.info("Using default MockMaker") +task(createTestResources) { + doLast { + // Configure MockMaker from environment (if specified), otherwise use default + def mockMakerFile = new File("$sourceSets.test.output.resourcesDir/mockito-extensions/org.mockito.plugins.MockMaker") + if (System.env.MOCK_MAKER != null && !System.env.MOCK_MAKER.endsWith("default")) { + logger.info("Using MockMaker ${System.env.MOCK_MAKER}") + mockMakerFile.parentFile.mkdirs() + mockMakerFile.createNewFile() + mockMakerFile.write(System.env.MOCK_MAKER) + } else { + logger.info("Using default MockMaker") + } + + // Configure MemberAccessor from environment (if specified), otherwise use default + def memberAccessorFile = new File("$sourceSets.test.output.resourcesDir/mockito-extensions/org.mockito.plugins.MemberAccessor") + if (System.env.MEMBER_ACCESSOR != null && !System.env.MEMBER_ACCESSOR.endsWith("default")) { + logger.info("Using MemberAccessor ${System.env.MEMBER_ACCESSOR}") + memberAccessorFile.parentFile.mkdirs() + memberAccessorFile.createNewFile() + memberAccessorFile.write(System.env.MEMBER_ACCESSOR) + } else { + logger.info("Using default MemberAccessor") + } } } task(removeTestResources).doLast { - def mockMakerFile = new File("$projectDir/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker") + def mockMakerFile = new File("$sourceSets.test.output.resourcesDir/mockito-extensions/org.mockito.plugins.MockMaker") if (mockMakerFile.exists()) { mockMakerFile.delete() } + + def memberAccessorFile = new File("$sourceSets.test.output.resourcesDir/mockito-extensions/org.mockito.plugins.MemberAccessor") + if (memberAccessorFile.exists()) { + memberAccessorFile.delete() + } } -compileTestJava.dependsOn createTestResources -compileTestJava.finalizedBy removeTestResources +processTestResources.finalizedBy(createTestResources) +test.finalizedBy(removeTestResources) diff --git a/gradle/root/coverage.gradle b/gradle/root/coverage.gradle index 87d0d7da01..4b9b041113 100644 --- a/gradle/root/coverage.gradle +++ b/gradle/root/coverage.gradle @@ -5,21 +5,13 @@ task mockitoCoverage(type: JacocoReport) { if (currentProject.name in ['android']) { return } - // We only run these tests on Java 9+ - if (currentProject.name in ['module-test'] && !JavaVersion.current().isJava9Compatible()) { - return - } - // We only run these tests on Java 8 - if (currentProject.name in ['errorprone'] && JavaVersion.current().isJava9Compatible()) { - return - } plugins.withId("java") { mockitoCoverage.sourceSets currentProject.sourceSets.main apply plugin: "jacoco" jacoco { - toolVersion = '0.8.7' + toolVersion = '0.8.8' if (currentProject != rootProject) { //already automatically enhanced in mockito main project as "java" plugin was applied before applyTo test @@ -43,8 +35,8 @@ task mockitoCoverage(type: JacocoReport) { } reports { - xml.enabled true - html.enabled true + xml.required = true + html.required = true html.destination file("${buildDir}/jacocoHtml") } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7454180f2a..249e5832f0 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 93a3251ccf..03ca076c8a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=9afb3ca688fc12c761a0e9e4321e4d24e977a4a8916c8a768b1fe05ddb4d6b66 -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.1-bin.zip +distributionSha256Sum=7ba68c54029790ab444b39d7e293d3236b2632631fb5f2e012bb28b4ff669e4b +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 1b6c787337..a69d9cb6c2 100755 --- a/gradlew +++ b/gradlew @@ -205,6 +205,12 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/gradlew.bat b/gradlew.bat index 107acd32c4..f127cfd49d 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,7 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -75,13 +75,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/settings.gradle.kts b/settings.gradle.kts index 313149df98..a0a66b7764 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,7 +2,8 @@ plugins { id("com.gradle.enterprise").version("3.3.4") } -include("inline", +include("subclass", + "inlineTest", "proxy", "extTest", "groovyTest", @@ -17,15 +18,13 @@ include("inline", "memory-test", "junitJupiterParallelTest", "osgi-test", - "bom") + "bom", + "errorprone", + "programmatic-test") -if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_11)) { - include("errorprone") -} else { - logger.info("Not including errorprone, which requires minimum JDK 11+") -} - -if (!JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_17) && (System.getenv("ANDROID_SDK_ROOT") != null || File(".local.properties").exists())) { +// https://developer.android.com/studio/command-line/variables#envar +// https://developer.android.com/studio/build#properties-files +if (System.getenv("ANDROID_HOME") != null || File("local.properties").exists()) { include("androidTest") } else { logger.info("Not including android test project due to missing SDK configuration") diff --git a/src/main/java/org/mockito/AdditionalAnswers.java b/src/main/java/org/mockito/AdditionalAnswers.java index 1ba790f1c0..63ab9bb483 100644 --- a/src/main/java/org/mockito/AdditionalAnswers.java +++ b/src/main/java/org/mockito/AdditionalAnswers.java @@ -276,7 +276,7 @@ public static Answer returnsArgAt(int position) { * This feature suffers from the same drawback as the spy. * The mock will call the delegate if you use regular when().then() stubbing style. * Since the real implementation is called this might have some side effects. - * Therefore you should use the doReturn|Throw|Answer|CallRealMethod stubbing style. Example: + * Therefore, you should use the doReturn|Throw|Answer|CallRealMethod stubbing style. Example: * *

      *   List listWithDelegate = mock(List.class, AdditionalAnswers.delegatesTo(awesomeList));
diff --git a/src/main/java/org/mockito/ArgumentCaptor.java b/src/main/java/org/mockito/ArgumentCaptor.java
index afb3add2ba..e9b347bd2e 100644
--- a/src/main/java/org/mockito/ArgumentCaptor.java
+++ b/src/main/java/org/mockito/ArgumentCaptor.java
@@ -35,12 +35,13 @@
  *
  * 

* Warning: it is recommended to use ArgumentCaptor with verification but not with stubbing. - * Using ArgumentCaptor with stubbing may decrease test readability because captor is created outside of assert (aka verify or 'then') block. - * Also it may reduce defect localization because if stubbed method was not called then no argument is captured. + * Using ArgumentCaptor with stubbing may decrease test readability because captor is created + * outside of assertion (aka verify or 'then') blocks. + * It may also reduce defect localization because if the stubbed method was not called, then no argument is captured. * *

* In a way ArgumentCaptor is related to custom argument matchers (see javadoc for {@link ArgumentMatcher} class). - * Both techniques can be used for making sure certain arguments were passed to mocks. + * Both techniques can be used for making sure certain arguments were passed to mock objects. * However, ArgumentCaptor may be a better fit if: *

    *
  • custom argument matcher is not likely to be reused
  • @@ -62,11 +63,12 @@ @CheckReturnValue public class ArgumentCaptor { - private final CapturingMatcher capturingMatcher = new CapturingMatcher(); + private final CapturingMatcher capturingMatcher; private final Class clazz; private ArgumentCaptor(Class clazz) { this.clazz = clazz; + this.capturingMatcher = new CapturingMatcher(clazz); } /** diff --git a/src/main/java/org/mockito/ArgumentMatcher.java b/src/main/java/org/mockito/ArgumentMatcher.java index d0324b6f3f..3c8ccdea00 100644 --- a/src/main/java/org/mockito/ArgumentMatcher.java +++ b/src/main/java/org/mockito/ArgumentMatcher.java @@ -10,7 +10,7 @@ * and reduce the risk of version incompatibility. * Migration guide is included close to the bottom of this javadoc. *

    - * For non-trivial method arguments used in stubbing or verification, you have following options + * For non-trivial method arguments used in stubbing or verification, you have the following options * (in no particular order): *

      *
    • refactor the code so that the interactions with collaborators are easier to test with mocks. @@ -102,14 +102,15 @@ * *
    • *
    - * What option is right for you? If you don't mind compile dependency to hamcrest - * then option b) is probably right for you. - * Your choice should not have big impact and is fully reversible - - * you can choose different option in future (and refactor the code) + * What option is right for you? If you don't mind having a compile-time dependency for Hamcrest, + * then the second option is probably right for you. + * Your choice should not have a big impact and is fully reversible - + * you can choose different option in future (and refactor the code)! * * @param type of argument * @since 2.1.0 */ +@FunctionalInterface public interface ArgumentMatcher { /** @@ -125,4 +126,44 @@ public interface ArgumentMatcher { * @return true if this matcher accepts the given argument. */ boolean matches(T argument); + + /** + * The type of the argument this matcher matches. + * + *

    This method is used to differentiate between a matcher used to match a raw vararg array parameter + * from a matcher used to match a single value passed as a vararg parameter. + * + *

    Where the matcher: + *

      + *
    • is at the parameter index of a vararg parameter
    • + *
    • is the last matcher passed
    • + *
    • this method returns a type assignable to the vararg parameter's raw type, i.e. its array type.
    • + *
    + * + * ...then the matcher is matched against the raw vararg parameter, rather than the first element of the raw parameter. + * + *

    For example: + * + *

    
    +     *  // Given vararg method with signature:
    +     *  int someVarargMethod(String... args);
    +     *
    +     *  // The following will match invocations with any number of parameters, i.e. any number of elements in the raw array.
    +     *  mock.someVarargMethod(isA(String[].class));
    +     *
    +     *  // The following will match invocations with a single parameter, i.e. one string in the raw array.
    +     *  mock.someVarargMethod(isA(String.class));
    +     *
    +     *  // The following will match invocations with two parameters, i.e. two strings in the raw array
    +     *  mock.someVarargMethod(isA(String.class), isA(String.class));
    +     * 
    + * + *

    Only matcher implementations that can conceptually match a raw vararg parameter should override this method. + * + * @return the type this matcher handles. The default value of {@link Void} means the type is not known. + * @since 4.11.0 + */ + default Class type() { + return Void.class; + } } diff --git a/src/main/java/org/mockito/ArgumentMatchers.java b/src/main/java/org/mockito/ArgumentMatchers.java index d969bc79e7..1743ce164b 100644 --- a/src/main/java/org/mockito/ArgumentMatchers.java +++ b/src/main/java/org/mockito/ArgumentMatchers.java @@ -14,6 +14,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Consumer; import java.util.regex.Pattern; import org.mockito.internal.matchers.Any; @@ -174,7 +175,7 @@ public static T any() { * @see #isNull() */ public static T any(Class type) { - reportMatcher(new InstanceOf.VarArgAware(type, "")); + reportMatcher(new InstanceOf(type, "")); return defaultValue(type); } @@ -699,6 +700,23 @@ public static T isNull() { return null; } + /** + * null argument. + * + *

    + * See examples in javadoc for {@link ArgumentMatchers} class + *

    + * + * @param type the type of the argument being matched. + * @return null. + * @see #isNotNull(Class) + * @since 4.11.0 + */ + public static T isNull(Class type) { + reportMatcher(new Null<>(type)); + return null; + } + /** * Not null argument. * @@ -717,6 +735,26 @@ public static T notNull() { return null; } + /** + * Not null argument. + * + *

    + * Alias to {@link ArgumentMatchers#isNotNull()} + *

    + * + *

    + * See examples in javadoc for {@link ArgumentMatchers} class + *

    + * + * @param type the type of the argument being matched. + * @return null. + * @since 4.11.0 + */ + public static T notNull(Class type) { + reportMatcher(new NotNull<>(type)); + return null; + } + /** * Not null argument. * @@ -735,6 +773,26 @@ public static T isNotNull() { return notNull(); } + /** + * Not null argument. + * + *

    + * Alias to {@link ArgumentMatchers#notNull(Class)} + *

    + * + *

    + * See examples in javadoc for {@link ArgumentMatchers} class + *

    + * + * @param type the type of the argument being matched. + * @return null. + * @see #isNull() + * @since 4.11.0 + */ + public static T isNotNull(Class type) { + return notNull(type); + } + /** * Argument that is either null or of the given type. * @@ -856,8 +914,24 @@ public static T argThat(ArgumentMatcher matcher) { } /** - * Allows creating custom char argument matchers. + * Allows creating custom argument matchers where matching is considered successful when the consumer given by parameter does not throw an exception. + *

    + * Typically used with {@link Mockito#verify(Object)} to execute assertions on parameters passed to the verified method invocation. * + * @param consumer executes assertions on the verified argument + * @return null. + */ + public static T assertArg(Consumer consumer) { + return argThat( + argument -> { + consumer.accept(argument); + return true; + }); + } + + /** + * Allows creating custom char argument matchers. + *

    * Note that {@link #argThat} will not work with primitive char matchers due to NullPointerException auto-unboxing caveat. *

    * See examples in javadoc for {@link ArgumentMatchers} class @@ -872,7 +946,7 @@ public static char charThat(ArgumentMatcher matcher) { /** * Allows creating custom boolean argument matchers. - * + *

    * Note that {@link #argThat} will not work with primitive boolean matchers due to NullPointerException auto-unboxing caveat. *

    * See examples in javadoc for {@link ArgumentMatchers} class @@ -887,7 +961,7 @@ public static boolean booleanThat(ArgumentMatcher matcher) { /** * Allows creating custom byte argument matchers. - * + *

    * Note that {@link #argThat} will not work with primitive byte matchers due to NullPointerException auto-unboxing caveat. *

    * See examples in javadoc for {@link ArgumentMatchers} class @@ -902,7 +976,7 @@ public static byte byteThat(ArgumentMatcher matcher) { /** * Allows creating custom short argument matchers. - * + *

    * Note that {@link #argThat} will not work with primitive short matchers due to NullPointerException auto-unboxing caveat. *

    * See examples in javadoc for {@link ArgumentMatchers} class @@ -917,7 +991,7 @@ public static short shortThat(ArgumentMatcher matcher) { /** * Allows creating custom int argument matchers. - * + *

    * Note that {@link #argThat} will not work with primitive int matchers due to NullPointerException auto-unboxing caveat. *

    * See examples in javadoc for {@link ArgumentMatchers} class @@ -932,7 +1006,7 @@ public static int intThat(ArgumentMatcher matcher) { /** * Allows creating custom long argument matchers. - * + *

    * Note that {@link #argThat} will not work with primitive long matchers due to NullPointerException auto-unboxing caveat. *

    * See examples in javadoc for {@link ArgumentMatchers} class @@ -947,7 +1021,7 @@ public static long longThat(ArgumentMatcher matcher) { /** * Allows creating custom float argument matchers. - * + *

    * Note that {@link #argThat} will not work with primitive float matchers due to NullPointerException auto-unboxing caveat. *

    * See examples in javadoc for {@link ArgumentMatchers} class @@ -962,7 +1036,7 @@ public static float floatThat(ArgumentMatcher matcher) { /** * Allows creating custom double argument matchers. - * + *

    * Note that {@link #argThat} will not work with primitive double matchers due to NullPointerException auto-unboxing caveat. *

    * See examples in javadoc for {@link ArgumentMatchers} class diff --git a/src/main/java/org/mockito/InOrder.java b/src/main/java/org/mockito/InOrder.java index 3cb047a4c4..ee71ae197c 100644 --- a/src/main/java/org/mockito/InOrder.java +++ b/src/main/java/org/mockito/InOrder.java @@ -12,13 +12,41 @@ * Allows verification in order. E.g: * *

    
    + * // Given
    + * First firstMock = mock(First.class);
    + * Second secondMock = mock(Second.class);
      * InOrder inOrder = inOrder(firstMock, secondMock);
      *
    + * // When
    + * firstMock.add("was called first");
    + * secondMock.add("was called second");
    + *
    + * // Then
      * inOrder.verify(firstMock).add("was called first");
      * inOrder.verify(secondMock).add("was called second");
    + * inOrder.verifyNoMoreInteractions();
    + * 
    + * + * Static mocks can be verified alongside non-static mocks. E.g: + * + *
    
    + * // Given
    + * First firstMock = mock(First.class);
    + * MockedStatic staticSecondMock = mockStatic(StaticSecond.class);
    + * InOrder inOrder = inOrder(firstMock, StaticSecond.class);
    + *
    + * // When
    + * firstMock.add("was called first");
    + * StaticSecond.doSomething("foobar");
    + *
    + * // Then
    + * inOrder.verify(firstMock).add("was called first");
    + * inOrder.verify(staticSecondMock, () -> StaticSecond.doSomething("foobar"));
    + * inOrder.verifyNoMoreInteractions();
      * 
    * - * As of Mockito 1.8.4 you can verifyNoMoreInteractions() in order-sensitive way. Read more: {@link InOrder#verifyNoMoreInteractions()} + * As of Mockito 1.8.4 you can verifyNoMoreInteractions() in order-sensitive way. Read more: + * {@link InOrder#verifyNoMoreInteractions()}. *

    * * See examples in javadoc for {@link Mockito} class diff --git a/src/main/java/org/mockito/Incubating.java b/src/main/java/org/mockito/Incubating.java index adf011abc7..ba209b3c41 100644 --- a/src/main/java/org/mockito/Incubating.java +++ b/src/main/java/org/mockito/Incubating.java @@ -5,6 +5,7 @@ package org.mockito; import java.lang.annotation.Documented; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -22,7 +23,13 @@ * and can change before release. * *

+ * + *

+ * Any components extending a class or interface annotated with this annotation should also + * be considered to be incubating, as the underlying implementation may be subject to future change + * before it is finalized. */ @Retention(RetentionPolicy.RUNTIME) +@Inherited @Documented public @interface Incubating {} diff --git a/src/main/java/org/mockito/InjectMocks.java b/src/main/java/org/mockito/InjectMocks.java index ea8f188985..ff412b8df6 100644 --- a/src/main/java/org/mockito/InjectMocks.java +++ b/src/main/java/org/mockito/InjectMocks.java @@ -158,6 +158,11 @@ * be it mocks/spies or real objects. *

* + *

+ * Elements annotated with this annotation can also be spied upon by also adding the {@link Spy} + * annotation to the element. + *

+ * * @see Mock * @see Spy * @see MockitoAnnotations#openMocks(Object) diff --git a/src/main/java/org/mockito/Mock.java b/src/main/java/org/mockito/Mock.java index e8469b830d..4d94edfe26 100644 --- a/src/main/java/org/mockito/Mock.java +++ b/src/main/java/org/mockito/Mock.java @@ -13,6 +13,7 @@ import java.lang.annotation.Target; import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.plugins.MockMaker; import org.mockito.stubbing.Answer; /** @@ -124,6 +125,20 @@ */ Strictness strictness() default Strictness.TEST_LEVEL_DEFAULT; + /** + * Mock will be created by the given {@link MockMaker}, see {@link MockSettings#mockMaker(String)}. + * + * @since 4.8.0 + */ + String mockMaker() default ""; + + /** + * Mock will not attempt to preserve all annotation metadata, see {@link MockSettings#withoutAnnotations()}. + * + * @since 5.3.0 + */ + boolean withoutAnnotations() default false; + enum Strictness { /** diff --git a/src/main/java/org/mockito/MockMakers.java b/src/main/java/org/mockito/MockMakers.java new file mode 100644 index 0000000000..2f5459d222 --- /dev/null +++ b/src/main/java/org/mockito/MockMakers.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2022 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito; + +import org.mockito.plugins.MockMaker; + +/** + * Constants for built-in implementations of {@code MockMaker}. + * You may use the constants of this class for {@link MockSettings#mockMaker(String)} or {@link Mock#mockMaker()}. + * The string values of these constants may also be used in the resource file mockito-extensions/org.mockito.plugins.MockMaker + * as described in the class documentation of {@link MockMaker}. + * + * @since 4.8.0 + */ +public final class MockMakers { + /** + * Inline mock maker which can mock final types, enums and final methods. + * This mock maker cannot mock native methods, + * and it does not support {@link MockSettings#extraInterfaces(Class[]) extra interfaces}. + * + * @see Mocking final types, enums and final methods + */ + public static final String INLINE = "mock-maker-inline"; + /** + * Proxy mock maker which avoids code generation, but can only mock interfaces. + * + * @see Avoiding code generation when restricting mocks to interfaces + */ + public static final String PROXY = "mock-maker-proxy"; + /** + * Subclass mock maker which mocks types by creating subclasses. + * This is the first built-in mock maker which has been provided by Mockito. + * Since this mock maker relies on subclasses, it cannot mock final classes and methods. + */ + public static final String SUBCLASS = "mock-maker-subclass"; + + private MockMakers() {} +} diff --git a/src/main/java/org/mockito/MockSettings.java b/src/main/java/org/mockito/MockSettings.java index f838a8d121..e9c75c3a1e 100644 --- a/src/main/java/org/mockito/MockSettings.java +++ b/src/main/java/org/mockito/MockSettings.java @@ -5,6 +5,7 @@ package org.mockito; import java.io.Serializable; +import java.lang.reflect.Type; import org.mockito.exceptions.misusing.PotentialStubbingProblem; import org.mockito.exceptions.misusing.UnnecessaryStubbingException; @@ -15,6 +16,7 @@ import org.mockito.listeners.VerificationStartedListener; import org.mockito.mock.MockCreationSettings; import org.mockito.mock.SerializableMode; +import org.mockito.plugins.MockMaker; import org.mockito.quality.Strictness; import org.mockito.stubbing.Answer; @@ -42,7 +44,7 @@ *
* {@link MockSettings} has been introduced for two reasons. * Firstly, to make it easy to add another mock setting when the demand comes. - * Secondly, to enable combining together different mock settings without introducing zillions of overloaded mock() methods. + * Secondly, to enable combining different mock settings without introducing zillions of overloaded mock() methods. */ @NotExtensible public interface MockSettings extends Serializable { @@ -63,7 +65,7 @@ public interface MockSettings extends Serializable { * Baz baz = (Baz) foo; * * - * @param interfaces extra interfaces the should implement. + * @param interfaces extra interfaces the mock should implement. * @return settings instance so that you can fluently specify other settings */ MockSettings extraInterfaces(Class... interfaces); @@ -93,7 +95,7 @@ public interface MockSettings extends Serializable { * * Sets the instance that will be spied. Actually copies the internal fields of the passed instance to the mock. *

- * As usual you are going to read the partial mock warning: + * As usual, you are going to read the partial mock warning: * Object oriented programming is more or less about tackling complexity by dividing the complexity into separate, specific, SRPy objects. * How does partial mock fit into this paradigm? Well, it just doesn't... * Partial mock usually means that the complexity has been moved to a different method on the same object. @@ -132,10 +134,10 @@ public interface MockSettings extends Serializable { /** * Specifies default answers to interactions. - * It's quite advanced feature and typically you don't need it to write decent tests. - * However it can be helpful when working with legacy systems. + * It's quite advanced feature, and typically you don't need it to write decent tests. + * However, it can be helpful when working with legacy systems. *

- * It is the default answer so it will be used only when you don't stub the method call. + * It is the default answer, so it will be used only when you don't stub the method call. * *


      *   Foo mock = mock(Foo.class, withSettings().defaultAnswer(RETURNS_SMART_NULLS));
@@ -208,7 +210,7 @@ public interface MockSettings extends Serializable {
     /**
      * Add stubbing lookup listener to the mock object.
      *
-     * Multiple listeners may be added and they will be notified orderly.
+     * Multiple listeners may be added, and they will be notified in an orderly fashion.
      *
      * For use cases and more info see {@link StubbingLookupListener}.
      *
@@ -227,7 +229,7 @@ public interface MockSettings extends Serializable {
      * Registers a listener for method invocations on this mock. The listener is
      * notified every time a method on this mock is called.
      * 

- * Multiple listeners may be added and they will be notified in the order they were supplied. + * Multiple listeners may be added, and they will be notified in the order they were supplied. * * Example: *


@@ -246,7 +248,7 @@ public interface MockSettings extends Serializable {
      * See {@link VerificationStartedListener} on how such listener can be useful.
      * 

* When multiple listeners are added, they are notified in order they were supplied. - * There is no reason to supply multiple listeners but we wanted to keep the API + * There is no reason to supply multiple listeners, but we wanted to keep the API * simple and consistent with {@link #invocationListeners(InvocationListener...)}. *

* Throws exception when any of the passed listeners is null or when the entire vararg array is null. @@ -312,7 +314,7 @@ public interface MockSettings extends Serializable { MockSettings outerInstance(Object outerClassInstance); /** - * By default, Mockito makes an attempt to preserve all annotation meta data on the mocked + * By default, Mockito makes an attempt to preserve all annotation metadata on the mocked * type and its methods to mirror the mocked type as closely as possible. If this is not * desired, this option can be used to disable this behavior. * @@ -381,4 +383,32 @@ public interface MockSettings extends Serializable { * @since 4.6.0 */ MockSettings strictness(Strictness strictness); + + /** + * Specifies the {@code MockMaker} for the mock. + * The default depends on your project as described in the class documentation of {@link MockMaker}. + * You should usually use the default, this option primarily exists to ease migrations. + * You may specify either one of the constants from {@link MockMakers}, + *

+     *     Object mock = Mockito.mock(Object.class, Mockito.withSettings()
+     *             .mockMaker(MockMakers.INLINE));
+     * 
+ * or the {@link Class#getName() binary name} of a class which implements the {@code MockMaker} interface. + *
+     *     Object mock = Mockito.mock(Object.class, Mockito.withSettings()
+     *             .mockMaker("org.awesome.mockito.AwesomeMockMaker"));
+     * 
+ * + * @param mockMaker the {@code MockMaker} to use for the mock + * @return settings instance so that you can fluently specify other settings + * @since 4.8.0 + */ + MockSettings mockMaker(String mockMaker); + + /** + * Specifies the generic type of the mock, preserving the information lost to Java type erasure. + * @param genericTypeToMock + * @return + */ + MockSettings genericTypeToMock(Type genericTypeToMock); } diff --git a/src/main/java/org/mockito/MockedConstruction.java b/src/main/java/org/mockito/MockedConstruction.java index 5c2ec2128d..ccf78c73a2 100644 --- a/src/main/java/org/mockito/MockedConstruction.java +++ b/src/main/java/org/mockito/MockedConstruction.java @@ -21,19 +21,52 @@ */ public interface MockedConstruction extends ScopedMock { + /** + * Get the constructed mocks. + * + * @return the constructed mocks. + */ List constructed(); + /** + * The context for a construction mock. + */ interface Context { int getCount(); + /** + * Get the constructor that is invoked during the mock creation. + * + * @return the constructor. + */ Constructor constructor(); + /** + * Get the arguments that were passed to the constructor. + * + * @return the arguments passed to the constructor, as a list. + */ List arguments(); } + /** + * Functional interface that consumes a newly created mock and the mock context. + *

+ * Used to attach behaviours to new mocks. + * + * @param the mock type. + */ + @FunctionalInterface interface MockInitializer { + /** + * Configure the mock. + * + * @param mock the newly created mock. + * @param context the mock context. + * @throws Throwable any exception that may be thrown. + */ void prepare(T mock, Context context) throws Throwable; } } diff --git a/src/main/java/org/mockito/MockedStatic.java b/src/main/java/org/mockito/MockedStatic.java index 9095556fd7..113bee2e3a 100644 --- a/src/main/java/org/mockito/MockedStatic.java +++ b/src/main/java/org/mockito/MockedStatic.java @@ -62,8 +62,17 @@ default void verify(Verification verification) { */ void verifyNoInteractions(); + /** + * Functional interface for a verification operation on a static mock. + */ + @FunctionalInterface interface Verification { + /** + * Apply the verification operation. + * + * @throws Throwable any exception that may be thrown. + */ void apply() throws Throwable; } } diff --git a/src/main/java/org/mockito/MockingDetails.java b/src/main/java/org/mockito/MockingDetails.java index c32e04e5c9..37a149100a 100644 --- a/src/main/java/org/mockito/MockingDetails.java +++ b/src/main/java/org/mockito/MockingDetails.java @@ -73,7 +73,7 @@ public interface MockingDetails { * What is 'stubbing'? * Stubbing is your when(x).then(y) declaration, e.g. configuring the mock to behave in a specific way, * when specific method with specific arguments is invoked on a mock. - * Typically stubbing is configuring mock to return X when method Y is invoked. + * Typically, stubbing is configuring mock to return X when method Y is invoked. *

* Why do you need to access stubbings of a mock? * In a normal workflow of creation clean tests, there is no need for this API. diff --git a/src/main/java/org/mockito/Mockito.java b/src/main/java/org/mockito/Mockito.java index 9f7383f232..1fce63e51e 100644 --- a/src/main/java/org/mockito/Mockito.java +++ b/src/main/java/org/mockito/Mockito.java @@ -33,8 +33,13 @@ import org.mockito.stubbing.OngoingStubbing; import org.mockito.stubbing.Stubber; import org.mockito.stubbing.VoidAnswer1; -import org.mockito.verification.*; +import org.mockito.verification.After; +import org.mockito.verification.Timeout; +import org.mockito.verification.VerificationAfterDelay; +import org.mockito.verification.VerificationMode; +import org.mockito.verification.VerificationWithTimeout; +import java.util.function.Consumer; import java.util.function.Function; /** @@ -67,7 +72,7 @@ * 11. Stubbing with callbacks
* 12. doReturn()|doThrow()|doAnswer()|doNothing()|doCallRealMethod() family of methods
* 13. Spying on real objects
- * 14. Changing default return values of unstubbed invocations (Since 1.7)
+ * 14. Changing default return values of un-stubbed invocations (Since 1.7)
* 15. Capturing arguments for further assertions (Since 1.8.0)
* 16. Real partial mocks (Since 1.8.0)
* 17. Resetting mocks (Since 1.8.0)
@@ -105,7 +110,9 @@ * 49. New API for mocking object construction (Since 3.5.0)
* 50. Avoiding code generation when restricting mocks to interfaces (Since 3.12.2)
* 51. New API for marking classes as unmockable (Since 4.1.0)
- * 52. New strictness attribute for @Mock annotation and MockSettings.strictness() methods (Since 4.6.0)
+ * 52. New strictness attribute for @Mock annotation and MockSettings.strictness() methods (Since 4.6.0)
+ * 53. Specifying mock maker for individual mocks (Since 4.8.0)
+ * 54. Mocking/spying without specifying class (Since 4.9.0)
* * *

0. Migrating to Mockito 2

@@ -220,7 +227,7 @@ * *
  • Stubbing can be overridden: for example common stubbing can go to * fixture setup but the test methods can override it. - * Please note that overridding stubbing is a potential code smell that points out too much stubbing
  • + * Please note that overriding stubbing is a potential code smell that points out too much stubbing * *
  • Once stubbed, the method will always return a stubbed value, regardless * of how many times it is called.
  • @@ -646,7 +653,7 @@ *
  • Mockito *does not* delegate calls to the passed real instance, instead it actually creates a copy of it. * So if you keep the real instance and interact with it, don't expect the spied to be aware of those interaction * and their effect on real instance state. - * The corollary is that when an *unstubbed* method is called *on the spy* but *not on the real instance*, + * The corollary is that when an *un-stubbed* method is called *on the spy* but *not on the real instance*, * you won't see any effects on the real instance. *
  • * @@ -659,7 +666,7 @@ * * * - *

    14. Changing default return values of unstubbed invocations (Since 1.7)

    + *

    14. Changing default return values of un-stubbed invocations (Since 1.7)

    * * You can create a mock with specified strategy for its return values. * It's quite an advanced feature and typically you don't need it to write decent tests. @@ -1586,7 +1593,7 @@ * released. To define mock behavior and to verify method invocations, use the MockedConstruction that is returned. *

    * - *

    50. Avoiding code generation when only interfaces are mocked (since 3.12.2)

    + *

    50. Avoiding code generation when only interfaces are mocked (since 3.12.2)

    * * The JVM offers the {@link java.lang.reflect.Proxy} facility for creating dynamic proxies of interface types. For most applications, Mockito * must be capable of mocking classes as supported by the default mock maker, or even final classes, as supported by the inline mock maker. To @@ -1609,7 +1616,7 @@ *

    * *

    52. - * New strictness attribute for @Mock annotation and MockSettings.strictness() methods (Since 4.6.0)

    + * New strictness attribute for @Mock annotation and MockSettings.strictness() methods (Since 4.6.0) * * You can now customize the strictness level for a single mock, either using `@Mock` annotation strictness attribute or * using `MockSettings.strictness()`. This can be useful if you want all of your mocks to be strict, @@ -1622,6 +1629,52 @@ * Foo mock = Mockito.mock(Foo.class, withSettings().strictness(Strictness.WARN)); *
    * + *

    53. + * Specifying mock maker for individual mocks (Since 4.8.0)

    + * + * You may encounter situations where you want to use a different mock maker for a specific test only. + * For example, you might want to migrate to the inline mock maker, but a few test do not work right away. + * In such case, you can (temporarily) use {@link MockSettings#mockMaker(String)} and {@link Mock#mockMaker()} + * to specify the mock maker for a specific mock which is causing the problem. + * + *
    
    + *   // using annotation
    + *   @Mock(mockMaker = MockMakers.SUBCLASS)
    + *   Foo mock;
    + *   // using MockSettings.withSettings()
    + *   Foo mock = Mockito.mock(Foo.class, withSettings().mockMaker(MockMakers.SUBCLASS));
    + * 
    + * + *

    54. + * Mocking/spying without specifying class (Since 4.9.0)

    + * + * Instead of calling method {@link Mockito#mock(Class)} or {@link Mockito#spy(Class)} with Class parameter, you can now + * now call method {@code mock()} or {@code spy()} without parameters: + * + *
    
    + *   Foo foo = Mockito.mock();
    + *   Bar bar = Mockito.spy();
    + * 
    + * + * Mockito will automatically detect the needed class. + *

    + * It works only if you assign result of {@code mock()} or {@code spy()} to a variable or field with an explicit type. + * With an implicit type, the Java compiler is unable to automatically determine the type of a mock and you need + * to pass in the {@code Class} explicitly. + *

    + * + *

    55. + * Verification with assertions (Since 5.3.0)

    + * + * To validate arguments during verification, instead of capturing them with {@link ArgumentCaptor}, you can now + * use {@link ArgumentMatchers#assertArg(Consumer)}}: + * + *
    
    + *   verify(serviceMock).doStuff(assertArg(param -> {
    + *     assertThat(param.getField1()).isEqualTo("foo");
    + *     assertThat(param.getField2()).isEqualTo("bar");
    + *   }));
    + * 
    */ @CheckReturnValue @SuppressWarnings("unchecked") @@ -1632,9 +1685,9 @@ public class Mockito extends ArgumentMatchers { /** * The default Answer of every mock if the mock was not stubbed. * - * Typically it just returns some empty value. + * Typically, it just returns some empty value. *

    - * {@link Answer} can be used to define the return values of unstubbed invocations. + * {@link Answer} can be used to define the return values of un-stubbed invocations. *

    * This implementation first tries the global configuration and if there is no global configuration then * it will use a default answer that returns zeros, empty collections, nulls, etc. @@ -1644,12 +1697,14 @@ public class Mockito extends ArgumentMatchers { /** * Optional Answer to be used with {@link Mockito#mock(Class, Answer)}. *

    - * {@link Answer} can be used to define the return values of unstubbed invocations. + * {@link Answer} can be used to define the return values of un-stubbed invocations. *

    * This implementation can be helpful when working with legacy code. - * Unstubbed methods often return null. If your code uses the object returned by an unstubbed call you get a NullPointerException. + * Un-stubbed methods often return null. If your code uses the object returned by an un-stubbed call, + * you get a NullPointerException. * This implementation of Answer returns SmartNull instead of null. - * SmartNull gives nicer exception message than NPE because it points out the line where unstubbed method was called. You just click on the stack trace. + * SmartNull gives nicer exception messages than NPEs, because it points out the + * line where the un-stubbed method was called. You just click on the stack trace. *

    * ReturnsSmartNulls first tries to return ordinary values (zeros, empty collections, empty string, etc.) * then it tries to return SmartNull. If the return type is final then plain null is returned. @@ -1658,15 +1713,15 @@ public class Mockito extends ArgumentMatchers { *

    
          *   Foo mock = mock(Foo.class, RETURNS_SMART_NULLS);
          *
    -     *   //calling unstubbed method here:
    +     *   //calling un-stubbed method here:
          *   Stuff stuff = mock.getStuff();
          *
    -     *   //using object returned by unstubbed call:
    +     *   //using object returned by un-stubbed call:
          *   stuff.doSomething();
          *
          *   //Above doesn't yield NullPointerException this time!
          *   //Instead, SmartNullPointerException is thrown.
    -     *   //Exception's cause links to unstubbed mock.getStuff() - just click on the stack trace.
    +     *   //Exception's cause links to un-stubbed mock.getStuff() - just click on the stack trace.
          * 
    */ public static final Answer RETURNS_SMART_NULLS = Answers.RETURNS_SMART_NULLS; @@ -1674,7 +1729,7 @@ public class Mockito extends ArgumentMatchers { /** * Optional Answer to be used with {@link Mockito#mock(Class, Answer)} *

    - * {@link Answer} can be used to define the return values of unstubbed invocations. + * {@link Answer} can be used to define the return values of un-stubbed invocations. *

    * This implementation can be helpful when working with legacy code. *

    @@ -1775,14 +1830,14 @@ public class Mockito extends ArgumentMatchers { * Optional Answer to be used with {@link Mockito#mock(Class, Answer)} * *

    - * {@link Answer} can be used to define the return values of unstubbed invocations. + * {@link Answer} can be used to define the return values of un-stubbed invocations. *

    * This implementation can be helpful when working with legacy code. - * When this implementation is used, unstubbed methods will delegate to the real implementation. + * When this implementation is used, un-stubbed methods will delegate to the real implementation. * This is a way to create a partial mock object that calls real methods by default. *

    - * As usual you are going to read the partial mock warning: - * Object oriented programming is more less tackling complexity by dividing the complexity into separate, specific, SRPy objects. + * As usual, you are going to read the partial mock warning: + * Object oriented programming is more-or-less tackling complexity by dividing the complexity into separate, specific, SRPy objects. * How does partial mock fit into this paradigm? Well, it just doesn't... * Partial mock usually means that the complexity has been moved to a different method on the same object. * In most cases, this is not the way you want to design your application. @@ -1884,6 +1939,71 @@ public class Mockito extends ArgumentMatchers { */ public static final Answer RETURNS_SELF = Answers.RETURNS_SELF; + /** + * Creates a mock object of the requested class or interface. + *

    + * See examples in javadoc for the {@link Mockito} class. + * + * @param reified don't pass any values to it. It's a trick to detect the class/interface you + * want to mock. + * @return the mock object. + * @since 4.9.0 + */ + @SafeVarargs + public static T mock(T... reified) { + return mock(withSettings(), reified); + } + + /** + * Creates a mock object of the requested class or interface with the given default answer. + *

    + * See examples in javadoc for the {@link Mockito} class. + * + * @param defaultAnswer the default answer to use. + * @param reified don't pass any values to it. It's a trick to detect the class/interface you + * want to mock. + * @return the mock object. + */ + @SafeVarargs + public static T mock(@SuppressWarnings("rawtypes") Answer defaultAnswer, T... reified) { + return mock(withSettings().defaultAnswer(defaultAnswer), reified); + } + + /** + * Creates a mock object of the requested class or interface with the given name. + *

    + * See examples in javadoc for the {@link Mockito} class. + * + * @param name the mock name to use. + * @param reified don't pass any values to it. It's a trick to detect the class/interface you + * want to mock. + * @return the mock object. + */ + @SafeVarargs + public static T mock(String name, T... reified) { + return mock(withSettings().name(name).defaultAnswer(RETURNS_DEFAULTS), reified); + } + + /** + * Creates a mock object of the requested class or interface with the given settings. + *

    + * See examples in javadoc for the {@link Mockito} class. + * + * @param settings the mock settings to use. + * @param reified don't pass any values to it. It's a trick to detect the class/interface you + * want to mock. + * @return the mock object. + */ + @SafeVarargs + public static T mock(MockSettings settings, T... reified) { + if (reified == null || reified.length > 0) { + throw new IllegalArgumentException( + "Please don't pass any values here. Java will detect class automagically."); + } + + return mock(getClassOf(reified), settings); + } + /** * Creates mock object of given class or interface. *

    @@ -1946,7 +2066,7 @@ public static MockingDetails mockingDetails(Object toInspect) { *

    See examples in javadoc for {@link Mockito} class

    * * @param classToMock class or interface to mock - * @param defaultAnswer default answer for unstubbed methods + * @param defaultAnswer default answer for un-stubbed methods * * @return mock object */ @@ -1957,7 +2077,7 @@ public static T mock(Class classToMock, Answer defaultAnswer) { /** * Creates a mock with some non-standard settings. *

    - * The number of configuration points for a mock grows + * The number of configuration points for a mock will grow, * so we need a fluent way to introduce new configuration without adding more and more overloaded Mockito.mock() methods. * Hence {@link MockSettings}. *

    
    @@ -1967,7 +2087,7 @@ public static  T mock(Class classToMock, Answer defaultAnswer) {
          * 
    * Use it carefully and occasionally. What might be reason your test needs non-standard mocks? * Is the code under test so complicated that it requires non-standard mocks? - * Wouldn't you prefer to refactor the code under test so it is testable in a simple way? + * Wouldn't you prefer to refactor the code under test, so that it is testable in a simple way? *

    * See also {@link Mockito#withSettings()} *

    @@ -1986,7 +2106,7 @@ public static T mock(Class classToMock, MockSettings mockSettings) { *

    * Real spies should be used carefully and occasionally, for example when dealing with legacy code. *

    - * As usual you are going to read the partial mock warning: + * As usual, you are going to read the partial mock warning: * Object oriented programming tackles complexity by dividing the complexity into separate, specific, SRPy objects. * How does partial mock fit into this paradigm? Well, it just doesn't... * Partial mock usually means that the complexity has been moved to a different method on the same object. @@ -2041,7 +2161,7 @@ public static T mock(Class classToMock, MockSettings mockSettings) { *

  • Mockito *does not* delegate calls to the passed real instance, instead it actually creates a copy of it. * So if you keep the real instance and interact with it, don't expect the spied to be aware of those interaction * and their effect on real instance state. - * The corollary is that when an *unstubbed* method is called *on the spy* but *not on the real instance*, + * The corollary is that when an *un-stubbed* method is called *on the spy* but *not on the real instance*, * you won't see any effects on the real instance.
  • * *
  • Watch out for final methods. @@ -2098,13 +2218,33 @@ public static T spy(Class classToSpy) { classToSpy, withSettings().useConstructor().defaultAnswer(CALLS_REAL_METHODS)); } + /** + * Please refer to the documentation of {@link #spy(Class)}. + * + * @param reified don't pass any values to it. It's a trick to detect the class/interface you want to mock. + * @return spy object + * @since 4.9.0 + */ + @SafeVarargs + public static T spy(T... reified) { + if (reified.length > 0) { + throw new IllegalArgumentException( + "Please don't pass any values here. Java will detect class automagically."); + } + return spy(getClassOf(reified)); + } + + private static Class getClassOf(T[] array) { + return (Class) array.getClass().getComponentType(); + } + /** * Creates a thread-local mock controller for all static methods of the given class or interface. * The returned object's {@link MockedStatic#close()} method must be called upon completing the * test or the mock will remain active on the current thread. *

    * Note: We recommend against mocking static methods of classes in the standard library or - * classes used by custom class loaders used to executed the block with the mocked class. A mock + * classes used by custom class loaders used to execute the block with the mocked class. A mock * maker might forbid mocking static methods of know classes that are known to cause problems. * Also, if a static method is a JVM-intrinsic, it cannot typically be mocked even if not * explicitly forbidden. @@ -2124,7 +2264,7 @@ public static MockedStatic mockStatic(Class classToMock) { * test or the mock will remain active on the current thread. *

    * Note: We recommend against mocking static methods of classes in the standard library or - * classes used by custom class loaders used to executed the block with the mocked class. A mock + * classes used by custom class loaders used to execute the block with the mocked class. A mock * maker might forbid mocking static methods of know classes that are known to cause problems. * Also, if a static method is a JVM-intrinsic, it cannot typically be mocked even if not * explicitly forbidden. @@ -2145,7 +2285,7 @@ public static MockedStatic mockStatic(Class classToMock, Answer defaul * test or the mock will remain active on the current thread. *

    * Note: We recommend against mocking static methods of classes in the standard library or - * classes used by custom class loaders used to executed the block with the mocked class. A mock + * classes used by custom class loaders used to execute the block with the mocked class. A mock * maker might forbid mocking static methods of know classes that are known to cause problems. * Also, if a static method is a JVM-intrinsic, it cannot typically be mocked even if not * explicitly forbidden. @@ -2166,7 +2306,7 @@ public static MockedStatic mockStatic(Class classToMock, String name) * test or the mock will remain active on the current thread. *

    * Note: We recommend against mocking static methods of classes in the standard library or - * classes used by custom class loaders used to executed the block with the mocked class. A mock + * classes used by custom class loaders used to execute the block with the mocked class. A mock * maker might forbid mocking static methods of know classes that are known to cause problems. * Also, if a static method is a JVM-intrinsic, it cannot typically be mocked even if not * explicitly forbidden. @@ -2234,7 +2374,7 @@ public static MockedConstruction mockConstruction(Class classToMock) { * See examples in javadoc for {@link Mockito} class * * @param classToMock non-abstract class of which constructions should be mocked. - * @param mockInitializer a callback to prepare a mock's methods after its instantiation. + * @param mockInitializer a callback to prepare the methods on a mock after its instantiation. * @return mock controller */ public static MockedConstruction mockConstruction( @@ -2284,7 +2424,7 @@ public static MockedConstruction mockConstruction( * * @param classToMock non-abstract class of which constructions should be mocked. * @param mockSettings the settings to use. - * @param mockInitializer a callback to prepare a mock's methods after its instantiation. + * @param mockInitializer a callback to prepare the methods on a mock after its instantiation. * @return mock controller */ public static MockedConstruction mockConstruction( @@ -2303,7 +2443,7 @@ public static MockedConstruction mockConstruction( * * @param classToMock non-abstract class of which constructions should be mocked. * @param mockSettingsFactory a function to create settings to use. - * @param mockInitializer a callback to prepare a mock's methods after its instantiation. + * @param mockInitializer a callback to prepare the methods on a mock after its instantiation. * @return mock controller */ public static MockedConstruction mockConstruction( @@ -2354,7 +2494,7 @@ public static MockedConstruction mockConstruction( *

    * Stubbing can be overridden: for example common stubbing can go to fixture * setup but the test methods can override it. - * Please note that overridding stubbing is a potential code smell that points out too much stubbing. + * Please note that overriding stubbing is a potential code smell that points out too much stubbing. *

    * Once stubbed, the method will always return stubbed value regardless * of how many times it is called. @@ -2506,7 +2646,7 @@ public static void clearInvocations(T... mocks) { * Some users who did a lot of classic, expect-run-verify mocking tend to use verifyNoMoreInteractions() very often, even in every test method. * verifyNoMoreInteractions() is not recommended to use in every test method. * verifyNoMoreInteractions() is a handy assertion from the interaction testing toolkit. Use it only when it's relevant. - * Abusing it leads to overspecified, less maintainable tests. + * Abusing it leads to over-specified, less maintainable tests. *

    * This method will also detect unverified invocations that occurred before the test method, * for example: in setUp(), @Before method or in constructor. @@ -2599,7 +2739,8 @@ public static Stubber doThrow(Class toBeThrown) { /** * Same as {@link #doThrow(Class)} but sets consecutive exception classes to be thrown. Remember to use - * doThrow() when you want to stub the void method to throw several exception of specified class. + * doThrow() when you want to stub the void method to throw several exceptions + * that are instances of the specified class. *

    * A new exception instance will be created for each method invocation. *

    @@ -2628,8 +2769,8 @@ public static Stubber doThrow( /** * Use doCallRealMethod() when you want to call the real implementation of a method. *

    - * As usual you are going to read the partial mock warning: - * Object oriented programming is more less tackling complexity by dividing the complexity into separate, specific, SRPy objects. + * As usual, you are going to read the partial mock warning: + * Object oriented programming is more-or-less tackling complexity by dividing the complexity into separate, specific, SRPy objects. * How does partial mock fit into this paradigm? Well, it just doesn't... * Partial mock usually means that the complexity has been moved to a different method on the same object. * In most cases, this is not the way you want to design your application. @@ -2767,7 +2908,7 @@ public static Stubber doNothing() { * * Above scenarios shows a tradeoff of Mockito's elegant syntax. Note that the scenarios are very rare, though. * Spying should be sporadic and overriding exception-stubbing is very rare. Not to mention that in general - * overridding stubbing is a potential code smell that points out too much stubbing. + * overriding stubbing is a potential code smell that points out too much stubbing. *

    * See examples in javadoc for {@link Mockito} class * @@ -2818,7 +2959,7 @@ public static Stubber doReturn(Object toBeReturned) { * * Above scenarios shows a trade-off of Mockito's elegant syntax. Note that the scenarios are very rare, though. * Spying should be sporadic and overriding exception-stubbing is very rare. Not to mention that in general - * overridding stubbing is a potential code smell that points out too much stubbing. + * overriding stubbing is a potential code smell that points out too much stubbing. *

    * See examples in javadoc for {@link Mockito} class * @@ -2869,7 +3010,7 @@ public static InOrder inOrder(Object... mocks) { * and provides other benefits. *

    * ignoreStubs() is sometimes useful when coupled with verifyNoMoreInteractions() or verification inOrder(). - * Helps avoiding redundant verification of stubbed calls - typically we're not interested in verifying stubs. + * Helps to avoid redundant verification of stubbed calls - typically we're not interested in verifying stubs. *

    * Warning, ignoreStubs() might lead to overuse of verifyNoMoreInteractions(ignoreStubs(...)); * Bear in mind that Mockito does not recommend bombarding every test with verifyNoMoreInteractions() @@ -3116,7 +3257,7 @@ public static VerificationWithTimeout timeout(long millis) { /** * Verification will be triggered after given amount of millis, allowing testing of async code. - * Useful when interactions with the mock object did not happened yet. + * Useful when interactions with the mock object have yet to occur. * Extensive use of {@code after()} method can be a code smell - there are better ways of testing concurrent code. *

    * Not yet implemented to work with InOrder verification. @@ -3276,7 +3417,7 @@ public static MockitoFramework framework() { /** * {@code MockitoSession} is an optional, highly recommended feature - * that helps driving cleaner tests by eliminating boilerplate code and adding extra validation. + * that drives writing cleaner tests by eliminating boilerplate code and adding extra validation. *

    * For more information, including use cases and sample code, see the javadoc for {@link MockitoSession}. * @@ -3298,7 +3439,7 @@ public static MockitoSessionBuilder mockitoSession() { * Most mocks in most tests don't need leniency and should happily prosper with {@link Strictness#STRICT_STUBS}. *

      *
    • If a specific stubbing needs to be lenient - use this method
    • - *
    • If a specific mock need to have stubbings lenient - use {@link MockSettings#lenient()}
    • + *
    • If a specific mock need to have lenient stubbings - use {@link MockSettings#strictness(Strictness)}
    • *
    • If a specific test method / test class needs to have all stubbings lenient * - configure strictness using our JUnit support ({@link MockitoJUnit} or Mockito Session ({@link MockitoSession})
    • * diff --git a/src/main/java/org/mockito/MockitoFramework.java b/src/main/java/org/mockito/MockitoFramework.java index ba814e6029..020186f052 100644 --- a/src/main/java/org/mockito/MockitoFramework.java +++ b/src/main/java/org/mockito/MockitoFramework.java @@ -26,11 +26,11 @@ public interface MockitoFramework { * Adds listener to Mockito. * For a list of supported listeners, see the interfaces that extend {@link MockitoListener}. *

      - * Listeners can be useful for engs that extend Mockito framework. + * Listeners can be useful for components that extend Mockito framework. * They are used in the implementation of unused stubbings warnings ({@link org.mockito.quality.MockitoHint}). *

      * Make sure you remove the listener when the job is complete, see {@link #removeListener(MockitoListener)}. - * Currently the listeners list is thread local so you need to remove listener from the same thread otherwise + * Currently, the listeners list is thread local, so you need to remove listener from the same thread otherwise * remove is ineffectual. * In typical scenarios, it is not a problem, because adding and removing listeners typically happens in the same thread. *

      @@ -56,7 +56,7 @@ public interface MockitoFramework { /** * When you add listener using {@link #addListener(MockitoListener)} make sure to remove it. - * Currently the listeners list is thread local so you need to remove listener from the same thread otherwise + * Currently, the listeners list is thread local, so you need to remove listener from the same thread otherwise * remove is ineffectual. * In typical scenarios, it is not a problem, because adding and removing listeners typically happens in the same thread. *

      @@ -92,7 +92,7 @@ public interface MockitoFramework { /** * Clears up internal state of all inline mocks. * This method is only meaningful if inline mock maker is in use. - * Otherwise this method is a no-op and need not be used. + * For all other intents and purposes, this method is a no-op and need not be used. *

      * This method is useful to tackle subtle memory leaks that are possible due to the nature of inline mocking * (issue #1619). diff --git a/src/main/java/org/mockito/MockitoSession.java b/src/main/java/org/mockito/MockitoSession.java index d3ad832c67..f87e07dcd4 100644 --- a/src/main/java/org/mockito/MockitoSession.java +++ b/src/main/java/org/mockito/MockitoSession.java @@ -17,12 +17,12 @@ /** * {@code MockitoSession} is an optional, highly recommended feature - * that helps driving cleaner tests by eliminating boilerplate code and adding extra validation. + * that drives writing cleaner tests by eliminating boilerplate code and adding extra validation. * If you already use {@link MockitoJUnitRunner} or {@link MockitoRule} * *you don't need* {@code MockitoSession} because it is used by the runner/rule. *

      * {@code MockitoSession} is a session of mocking, during which the user creates and uses Mockito mocks. - * Typically the session is an execution of a single test method. + * Typically, the session is an execution of a single test method. * {@code MockitoSession} initializes mocks, validates usage and detects incorrect stubbing. * When the session is started it must be concluded with {@link #finishMocking()} * otherwise {@link UnfinishedMockingSessionException} is triggered when the next session is created. diff --git a/src/main/java/org/mockito/NotExtensible.java b/src/main/java/org/mockito/NotExtensible.java index 39191ac481..f5110f5d57 100644 --- a/src/main/java/org/mockito/NotExtensible.java +++ b/src/main/java/org/mockito/NotExtensible.java @@ -19,7 +19,7 @@ * Public types are all types that are *not* under "org.mockito.internal.*" package. *

      * Absence of {@code NotExtensible} annotation on a type *does not* mean it is intended to be extended. - * The annotation has been introduced late and therefore it is not used frequently in the codebase. + * The annotation has been introduced late, and therefore it is not used frequently in the codebase. * Many public types from Mockito API are not intended for extension, even though they do not have this annotation applied. * * @since 2.10.0 diff --git a/src/main/java/org/mockito/Spy.java b/src/main/java/org/mockito/Spy.java index aa485a355d..fa5e6e5ad0 100644 --- a/src/main/java/org/mockito/Spy.java +++ b/src/main/java/org/mockito/Spy.java @@ -14,7 +14,7 @@ import org.mockito.junit.MockitoJUnitRunner; /** - * Allows shorthand wrapping of field instances in an spy object. + * Allows shorthand wrapping of field instances in a spy object. * *

      * Example: @@ -79,7 +79,7 @@ *

    • Mockito *does not* delegate calls to the passed real instance, instead it actually creates a copy of it. * So if you keep the real instance and interact with it, don't expect the spied to be aware of those interaction * and their effect on real instance state. - * The corollary is that when an *unstubbed* method is called *on the spy* but *not on the real instance*, + * The corollary is that when an *un-stubbed* method is called *on the spy* but *not on the real instance*, * you won't see any effects on the real instance.
    • * *
    • Watch out for final methods. diff --git a/src/main/java/org/mockito/configuration/IMockitoConfiguration.java b/src/main/java/org/mockito/configuration/IMockitoConfiguration.java index 53664940ea..6e16d350f1 100644 --- a/src/main/java/org/mockito/configuration/IMockitoConfiguration.java +++ b/src/main/java/org/mockito/configuration/IMockitoConfiguration.java @@ -13,7 +13,7 @@ * In most cases you don't really need to configure Mockito. For example in case of working with legacy code, * when you might want to have different 'mocking style' this interface might be helpful. * A reason of configuring Mockito might be if you disagree with the {@link org.mockito.Answers#RETURNS_DEFAULTS} - * unstubbed mocks return. + * un-stubbed mocks return. * *

      * To configure Mockito create exactly org.mockito.configuration.MockitoConfiguration class @@ -38,7 +38,7 @@ public interface IMockitoConfiguration { /** - * Allows configuring the default answers of unstubbed invocations + * Allows configuring the default answers of un-stubbed invocations *

      * See javadoc for {@link IMockitoConfiguration} */ diff --git a/src/main/java/org/mockito/exceptions/stacktrace/StackTraceCleaner.java b/src/main/java/org/mockito/exceptions/stacktrace/StackTraceCleaner.java index a3229a8c80..467809aa01 100644 --- a/src/main/java/org/mockito/exceptions/stacktrace/StackTraceCleaner.java +++ b/src/main/java/org/mockito/exceptions/stacktrace/StackTraceCleaner.java @@ -25,4 +25,29 @@ public interface StackTraceCleaner { * @return whether the element should be excluded from cleaned stack trace. */ boolean isIn(StackTraceElement candidate); + + /** + * It's recommended to override this method in subclasses to avoid potentially costly re-boxing operations. + */ + default boolean isIn(StackFrameMetadata candidate) { + return isIn( + new StackTraceElement( + candidate.getClassName(), + candidate.getMethodName(), + candidate.getFileName(), + candidate.getLineNumber())); + } + + /** + * Very similar to the StackFrame class declared on the StackWalker api. + */ + interface StackFrameMetadata { + String getClassName(); + + String getMethodName(); + + String getFileName(); + + int getLineNumber(); + } } diff --git a/src/main/java/org/mockito/hamcrest/MockitoHamcrest.java b/src/main/java/org/mockito/hamcrest/MockitoHamcrest.java index b8ebf8f97d..3624ba20c4 100644 --- a/src/main/java/org/mockito/hamcrest/MockitoHamcrest.java +++ b/src/main/java/org/mockito/hamcrest/MockitoHamcrest.java @@ -42,6 +42,24 @@ * Due to how java works we don't really have a clean way of detecting this scenario and protecting the user from this problem. * Hopefully, the javadoc describes the problem and solution well. * If you have an idea how to fix the problem, let us know via the mailing list or the issue tracker. + *

      + * By default, a matcher passed to a varargs parameter will match against the first element in the varargs array. + * To match against the raw varargs array pass the type of the varargs parameter to {@link MockitoHamcrest#argThat(Matcher, Class)} + *

      + * For example, to match any number of {@code String} values: + *

      + *     import static org.hamcrest.CoreMatchers.isA;
      + *     import static org.mockito.hamcrest.MockitoHamcrest.argThat;
      + *
      + *     // Given:
      + *     void varargMethod(String... args);
      + *
      + *     //stubbing
      + *     when(mock.varargMethod(argThat(isA(String[].class), String[].class));
      + *
      + *     //verification
      + *     verify(mock).giveMe(argThat(isA(String[].class), String[].class));
      + * 
      * * @since 2.1.0 */ @@ -62,6 +80,26 @@ public static T argThat(Matcher matcher) { return (T) defaultValue(genericTypeOfMatcher(matcher.getClass())); } + /** + * Allows matching arguments with hamcrest matchers. + *

      + * This variant can be used to pass an explicit {@code type}, + * which can be useful to provide a matcher that matches against all + * elements in a varargs parameter. + *

      + * See examples in javadoc for {@link MockitoHamcrest} class + * + * @param matcher decides whether argument matches + * @param type the type the matcher matches. + * @return null or default value for primitive (0, false, etc.) + * @since 5.0.0 + */ + @SuppressWarnings("unchecked") + public static T argThat(Matcher matcher, Class type) { + reportMatcher(matcher, type); + return (T) defaultValue(genericTypeOfMatcher(matcher.getClass())); + } + /** * Enables integrating hamcrest matchers that match primitive char arguments. * Note that {@link #argThat} will not work with primitive char matchers due to NullPointerException auto-unboxing caveat. @@ -175,9 +213,15 @@ public static double doubleThat(Matcher matcher) { } private static void reportMatcher(Matcher matcher) { - mockingProgress() - .getArgumentMatcherStorage() - .reportMatcher(new HamcrestArgumentMatcher(matcher)); + reportMatcher(new HamcrestArgumentMatcher(matcher)); + } + + private static void reportMatcher(Matcher matcher, Class type) { + reportMatcher(new HamcrestArgumentMatcher(matcher, type)); + } + + private static void reportMatcher(final HamcrestArgumentMatcher matcher) { + mockingProgress().getArgumentMatcherStorage().reportMatcher(matcher); } private MockitoHamcrest() {} diff --git a/src/main/java/org/mockito/internal/MockedConstructionImpl.java b/src/main/java/org/mockito/internal/MockedConstructionImpl.java index 47bd8089c6..5541ba07cd 100644 --- a/src/main/java/org/mockito/internal/MockedConstructionImpl.java +++ b/src/main/java/org/mockito/internal/MockedConstructionImpl.java @@ -11,7 +11,7 @@ import org.mockito.MockedConstruction; import org.mockito.exceptions.base.MockitoException; -import org.mockito.internal.debugging.LocationImpl; +import org.mockito.internal.debugging.LocationFactory; import org.mockito.invocation.Location; import org.mockito.plugins.MockMaker; @@ -21,7 +21,7 @@ public final class MockedConstructionImpl implements MockedConstruction { private boolean closed; - private final Location location = new LocationImpl(); + private final Location location = LocationFactory.create(); protected MockedConstructionImpl(MockMaker.ConstructionMockControl control) { this.control = control; diff --git a/src/main/java/org/mockito/internal/MockedStaticImpl.java b/src/main/java/org/mockito/internal/MockedStaticImpl.java index fbfb54b004..f6705fb81a 100644 --- a/src/main/java/org/mockito/internal/MockedStaticImpl.java +++ b/src/main/java/org/mockito/internal/MockedStaticImpl.java @@ -17,7 +17,7 @@ import org.mockito.Mockito; import org.mockito.exceptions.base.MockitoAssertionError; import org.mockito.exceptions.base.MockitoException; -import org.mockito.internal.debugging.LocationImpl; +import org.mockito.internal.debugging.LocationFactory; import org.mockito.internal.listeners.VerificationStartedNotifier; import org.mockito.internal.progress.MockingProgress; import org.mockito.internal.stubbing.InvocationContainerImpl; @@ -35,7 +35,7 @@ public final class MockedStaticImpl implements MockedStatic { private boolean closed; - private final Location location = new LocationImpl(); + private final Location location = LocationFactory.create(); protected MockedStaticImpl(MockMaker.StaticMockControl control) { this.control = control; diff --git a/src/main/java/org/mockito/internal/MockitoCore.java b/src/main/java/org/mockito/internal/MockitoCore.java index fff3b7667e..fd39f6a4a2 100644 --- a/src/main/java/org/mockito/internal/MockitoCore.java +++ b/src/main/java/org/mockito/internal/MockitoCore.java @@ -22,7 +22,6 @@ import static org.mockito.internal.util.MockUtil.getMockHandler; import static org.mockito.internal.util.MockUtil.isMock; import static org.mockito.internal.util.MockUtil.resetMock; -import static org.mockito.internal.util.MockUtil.typeMockabilityOf; import static org.mockito.internal.verification.VerificationModeFactory.noInteractions; import static org.mockito.internal.verification.VerificationModeFactory.noMoreInteractions; @@ -31,6 +30,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.function.Function; import org.mockito.InOrder; import org.mockito.MockSettings; @@ -67,10 +67,6 @@ import org.mockito.stubbing.Stubber; import org.mockito.verification.VerificationMode; -import java.util.Arrays; -import java.util.List; -import java.util.function.Function; - @SuppressWarnings("unchecked") public class MockitoCore { @@ -78,10 +74,6 @@ public class MockitoCore { private static final Set> MOCKABLE_CLASSES = Collections.synchronizedSet(new HashSet<>()); - public boolean isTypeMockable(Class typeToMock) { - return typeMockabilityOf(typeToMock).mockable(); - } - public T mock(Class typeToMock, MockSettings settings) { if (!(settings instanceof MockSettingsImpl)) { throw new IllegalArgumentException( @@ -160,6 +152,14 @@ public MockedConstruction mockConstruction( + "At the moment, you cannot provide your own implementations of that class."); } MockSettingsImpl impl = MockSettingsImpl.class.cast(value); + String mockMaker = impl.getMockMaker(); + if (mockMaker != null) { + throw new IllegalArgumentException( + "Unexpected MockMaker '" + + mockMaker + + "'\n" + + "At the moment, you cannot override the MockMaker for construction mocks."); + } return impl.build(typeToMock); }; MockMaker.ConstructionMockControl control = diff --git a/src/main/java/org/mockito/internal/configuration/GlobalConfiguration.java b/src/main/java/org/mockito/internal/configuration/GlobalConfiguration.java index 92f5a54eb4..d5ad75c93b 100644 --- a/src/main/java/org/mockito/internal/configuration/GlobalConfiguration.java +++ b/src/main/java/org/mockito/internal/configuration/GlobalConfiguration.java @@ -43,7 +43,7 @@ private IMockitoConfiguration createConfig() { } public static void validate() { - new GlobalConfiguration(); + GlobalConfiguration unused = new GlobalConfiguration(); } public org.mockito.plugins.AnnotationEngine tryGetPluginAnnotationEngine() { diff --git a/src/main/java/org/mockito/internal/configuration/MockAnnotationProcessor.java b/src/main/java/org/mockito/internal/configuration/MockAnnotationProcessor.java index 66c3f31d12..f61f4a7bd9 100644 --- a/src/main/java/org/mockito/internal/configuration/MockAnnotationProcessor.java +++ b/src/main/java/org/mockito/internal/configuration/MockAnnotationProcessor.java @@ -53,6 +53,14 @@ public static Object processAnnotationForMock( if (annotation.strictness() != Mock.Strictness.TEST_LEVEL_DEFAULT) { mockSettings.strictness(Strictness.valueOf(annotation.strictness().toString())); } + if (!annotation.mockMaker().isEmpty()) { + mockSettings.mockMaker(annotation.mockMaker()); + } + if (annotation.withoutAnnotations()) { + mockSettings.withoutAnnotations(); + } + + mockSettings.genericTypeToMock(genericType.get()); // see @Mock answer default value mockSettings.defaultAnswer(annotation.answer()); diff --git a/src/main/java/org/mockito/internal/configuration/SpyAnnotationEngine.java b/src/main/java/org/mockito/internal/configuration/SpyAnnotationEngine.java index a88ad63b84..cd51942581 100644 --- a/src/main/java/org/mockito/internal/configuration/SpyAnnotationEngine.java +++ b/src/main/java/org/mockito/internal/configuration/SpyAnnotationEngine.java @@ -83,9 +83,11 @@ public AutoCloseable process(Class context, Object testInstance) { } private static Object spyInstance(Field field, Object instance) { + // TODO: Add mockMaker option for @Spy annotation (#2740) return Mockito.mock( instance.getClass(), withSettings() + .genericTypeToMock(field.getGenericType()) .spiedInstance(instance) .defaultAnswer(CALLS_REAL_METHODS) .name(field.getName())); @@ -93,8 +95,12 @@ private static Object spyInstance(Field field, Object instance) { private static Object spyNewInstance(Object testInstance, Field field) throws InstantiationException, IllegalAccessException, InvocationTargetException { + // TODO: Add mockMaker option for @Spy annotation (#2740) MockSettings settings = - withSettings().defaultAnswer(CALLS_REAL_METHODS).name(field.getName()); + withSettings() + .genericTypeToMock(field.getGenericType()) + .defaultAnswer(CALLS_REAL_METHODS) + .name(field.getName()); Class type = field.getType(); if (type.isInterface()) { return Mockito.mock(type, settings.useConstructor()); diff --git a/src/main/java/org/mockito/internal/configuration/injection/PropertyAndSetterInjection.java b/src/main/java/org/mockito/internal/configuration/injection/PropertyAndSetterInjection.java index 7f267d2c5e..ed32c8eea4 100644 --- a/src/main/java/org/mockito/internal/configuration/injection/PropertyAndSetterInjection.java +++ b/src/main/java/org/mockito/internal/configuration/injection/PropertyAndSetterInjection.java @@ -81,6 +81,7 @@ public boolean processInjection( injectMockCandidates( fieldClass, fieldInstanceNeedingInjection, + injectMocksField, newMockSafeHashSet(mockCandidates)); fieldClass = fieldClass.getSuperclass(); } @@ -100,24 +101,32 @@ private FieldInitializationReport initializeInjectMocksField(Field field, Object } private boolean injectMockCandidates( - Class awaitingInjectionClazz, Object injectee, Set mocks) { + Class awaitingInjectionClazz, + Object injectee, + Field injectMocksField, + Set mocks) { boolean injectionOccurred; List orderedCandidateInjecteeFields = orderedInstanceFieldsFrom(awaitingInjectionClazz); // pass 1 injectionOccurred = injectMockCandidatesOnFields( - mocks, injectee, false, orderedCandidateInjecteeFields); + mocks, injectee, injectMocksField, false, orderedCandidateInjecteeFields); // pass 2 injectionOccurred |= injectMockCandidatesOnFields( - mocks, injectee, injectionOccurred, orderedCandidateInjecteeFields); + mocks, + injectee, + injectMocksField, + injectionOccurred, + orderedCandidateInjecteeFields); return injectionOccurred; } private boolean injectMockCandidatesOnFields( Set mocks, Object injectee, + Field injectMocksField, boolean injectionOccurred, List orderedCandidateInjecteeFields) { for (Iterator it = orderedCandidateInjecteeFields.iterator(); it.hasNext(); ) { @@ -125,7 +134,11 @@ private boolean injectMockCandidatesOnFields( Object injected = mockCandidateFilter .filterCandidate( - mocks, candidateField, orderedCandidateInjecteeFields, injectee) + mocks, + candidateField, + orderedCandidateInjecteeFields, + injectee, + injectMocksField) .thenInject(); if (injected != null) { injectionOccurred |= true; diff --git a/src/main/java/org/mockito/internal/configuration/injection/SpyOnInjectedFieldsHandler.java b/src/main/java/org/mockito/internal/configuration/injection/SpyOnInjectedFieldsHandler.java index 73f3004c19..bbfe88b85a 100644 --- a/src/main/java/org/mockito/internal/configuration/injection/SpyOnInjectedFieldsHandler.java +++ b/src/main/java/org/mockito/internal/configuration/injection/SpyOnInjectedFieldsHandler.java @@ -42,6 +42,7 @@ protected boolean processInjection(Field field, Object fieldOwner, Set m // B. protect against multiple use of MockitoAnnotations.openMocks() Mockito.reset(instance); } else { + // TODO: Add mockMaker option for @Spy annotation (#2740) Object mock = Mockito.mock( instance.getClass(), diff --git a/src/main/java/org/mockito/internal/configuration/injection/filter/MockCandidateFilter.java b/src/main/java/org/mockito/internal/configuration/injection/filter/MockCandidateFilter.java index 470a42ff17..82c913695e 100644 --- a/src/main/java/org/mockito/internal/configuration/injection/filter/MockCandidateFilter.java +++ b/src/main/java/org/mockito/internal/configuration/injection/filter/MockCandidateFilter.java @@ -13,5 +13,6 @@ OngoingInjector filterCandidate( Collection mocks, Field candidateFieldToBeInjected, List allRemainingCandidateFields, - Object injectee); + Object injectee, + Field injectMocksField); } diff --git a/src/main/java/org/mockito/internal/configuration/injection/filter/NameBasedCandidateFilter.java b/src/main/java/org/mockito/internal/configuration/injection/filter/NameBasedCandidateFilter.java index 125b9595ef..b2cc6c75d5 100644 --- a/src/main/java/org/mockito/internal/configuration/injection/filter/NameBasedCandidateFilter.java +++ b/src/main/java/org/mockito/internal/configuration/injection/filter/NameBasedCandidateFilter.java @@ -23,7 +23,8 @@ public OngoingInjector filterCandidate( final Collection mocks, final Field candidateFieldToBeInjected, final List allRemainingCandidateFields, - final Object injectee) { + final Object injectee, + final Field injectMocksField) { if (mocks.size() == 1 && anotherCandidateMatchesMockName( mocks, candidateFieldToBeInjected, allRemainingCandidateFields)) { @@ -34,7 +35,8 @@ && anotherCandidateMatchesMockName( tooMany(mocks) ? selectMatchingName(mocks, candidateFieldToBeInjected) : mocks, candidateFieldToBeInjected, allRemainingCandidateFields, - injectee); + injectee, + injectMocksField); } private boolean tooMany(Collection mocks) { diff --git a/src/main/java/org/mockito/internal/configuration/injection/filter/TerminalMockCandidateFilter.java b/src/main/java/org/mockito/internal/configuration/injection/filter/TerminalMockCandidateFilter.java index ec9dadcfa6..5726ee2015 100644 --- a/src/main/java/org/mockito/internal/configuration/injection/filter/TerminalMockCandidateFilter.java +++ b/src/main/java/org/mockito/internal/configuration/injection/filter/TerminalMockCandidateFilter.java @@ -28,7 +28,8 @@ public OngoingInjector filterCandidate( final Collection mocks, final Field candidateFieldToBeInjected, final List allRemainingCandidateFields, - final Object injectee) { + final Object injectee, + final Field injectMocksField) { if (mocks.size() == 1) { final Object matchingMock = mocks.iterator().next(); diff --git a/src/main/java/org/mockito/internal/configuration/injection/filter/TypeBasedCandidateFilter.java b/src/main/java/org/mockito/internal/configuration/injection/filter/TypeBasedCandidateFilter.java index 9ba0fba986..a66ab52144 100644 --- a/src/main/java/org/mockito/internal/configuration/injection/filter/TypeBasedCandidateFilter.java +++ b/src/main/java/org/mockito/internal/configuration/injection/filter/TypeBasedCandidateFilter.java @@ -4,10 +4,20 @@ */ package org.mockito.internal.configuration.injection.filter; +import static org.mockito.internal.exceptions.Reporter.moreThanOneMockCandidate; + import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.stream.Stream; + +import org.mockito.internal.util.MockUtil; public class TypeBasedCandidateFilter implements MockCandidateFilter { @@ -17,20 +27,169 @@ public TypeBasedCandidateFilter(MockCandidateFilter next) { this.next = next; } + protected boolean isCompatibleTypes(Type typeToMock, Type mockType, Field injectMocksField) { + boolean result = false; + if (typeToMock instanceof ParameterizedType) { + if (mockType instanceof ParameterizedType) { + // ParameterizedType.equals() is documented as: + // "Instances of classes that implement this interface must implement + // an equals() method that equates any two instances that share the + // same generic type declaration and have equal type parameters." + // Unfortunately, e.g. Wildcard parameter "?" doesn't equal java.lang.String, + // and e.g. Set doesn't equal TreeSet, so roll our own comparison if + // ParameterizedTypeImpl.equals() returns false + if (typeToMock.equals(mockType)) { + result = true; + } else { + ParameterizedType genericTypeToMock = (ParameterizedType) typeToMock; + ParameterizedType genericMockType = (ParameterizedType) mockType; + Type[] actualTypeArguments = genericTypeToMock.getActualTypeArguments(); + Type[] actualTypeArguments2 = genericMockType.getActualTypeArguments(); + // Recurse on type parameters, so we properly test whether e.g. Wildcard bounds + // have a match + result = + recurseOnTypeArguments( + injectMocksField, actualTypeArguments, actualTypeArguments2); + } + } else { + // mockType is a non-parameterized Class, i.e. a concrete class. + // so walk concrete class' type hierarchy + Class concreteMockClass = (Class) mockType; + Stream mockSuperTypes = getSuperTypes(concreteMockClass); + result = + mockSuperTypes.anyMatch( + mockSuperType -> + isCompatibleTypes( + typeToMock, mockSuperType, injectMocksField)); + } + } else if (typeToMock instanceof WildcardType) { + WildcardType wildcardTypeToMock = (WildcardType) typeToMock; + Type[] upperBounds = wildcardTypeToMock.getUpperBounds(); + result = + Arrays.stream(upperBounds) + .anyMatch(t -> isCompatibleTypes(t, mockType, injectMocksField)); + } else if (typeToMock instanceof Class && mockType instanceof Class) { + result = ((Class) typeToMock).isAssignableFrom((Class) mockType); + } // no need to check for GenericArrayType, as Mockito cannot mock this anyway + + return result; + } + + private Stream getSuperTypes(Class concreteMockClass) { + Stream mockInterfaces = Arrays.stream(concreteMockClass.getGenericInterfaces()); + Stream mockSuperTypes = + Stream.concat(mockInterfaces, Stream.of(concreteMockClass.getGenericSuperclass())); + return mockSuperTypes; + } + + private boolean recurseOnTypeArguments( + Field injectMocksField, Type[] actualTypeArguments, Type[] actualTypeArguments2) { + boolean isCompatible = true; + for (int i = 0; i < actualTypeArguments.length; i++) { + Type actualTypeArgument = actualTypeArguments[i]; + Type actualTypeArgument2 = actualTypeArguments2[i]; + if (actualTypeArgument instanceof TypeVariable) { + TypeVariable typeVariable = (TypeVariable) actualTypeArgument; + // this is a TypeVariable declared by the class under test that turned + // up in one of its fields, + // e.g. class ClassUnderTest { List tList; Set tSet} + // The TypeVariable`s actual type is declared by the field containing + // the object under test, i.e. the field annotated with @InjectMocks + // e.g. @InjectMocks ClassUnderTest underTest = .. + + Type genericType = injectMocksField.getGenericType(); + if (genericType instanceof ParameterizedType) { + Type[] injectMocksFieldTypeParameters = + ((ParameterizedType) genericType).getActualTypeArguments(); + // Find index of given TypeVariable where it was defined, e.g. 0 for T1 in + // ClassUnderTest + // (we're always able to find it, otherwise test class wouldn't have compiled)) + TypeVariable[] genericTypeParameters = + injectMocksField.getType().getTypeParameters(); + int variableIndex = -1; + for (int i2 = 0; i2 < genericTypeParameters.length; i2++) { + if (genericTypeParameters[i2].equals(typeVariable)) { + variableIndex = i2; + break; + } + } + // now test whether actual type for the type variable is compatible, e.g. for + // class ClassUnderTest {..} + // T1 would be the String in + // ClassUnderTest underTest = .. + isCompatible &= + isCompatibleTypes( + injectMocksFieldTypeParameters[variableIndex], + actualTypeArgument2, + injectMocksField); + } else { + // must be a concrete class, recurse on super types that may have type + // parameters + isCompatible &= + getSuperTypes((Class) genericType) + .anyMatch( + superType -> + isCompatibleTypes( + superType, + actualTypeArgument2, + injectMocksField)); + } + } else { + isCompatible &= + isCompatibleTypes( + actualTypeArgument, actualTypeArgument2, injectMocksField); + } + } + return isCompatible; + } + @Override public OngoingInjector filterCandidate( final Collection mocks, final Field candidateFieldToBeInjected, final List allRemainingCandidateFields, - final Object injectee) { + final Object injectee, + final Field injectMocksField) { List mockTypeMatches = new ArrayList<>(); for (Object mock : mocks) { if (candidateFieldToBeInjected.getType().isAssignableFrom(mock.getClass())) { - mockTypeMatches.add(mock); - } + Type mockType = MockUtil.getMockSettings(mock).getGenericTypeToMock(); + Type typeToMock = candidateFieldToBeInjected.getGenericType(); + boolean bothHaveTypeInfo = typeToMock != null && mockType != null; + if (bothHaveTypeInfo) { + // be more specific if generic type information is available + if (isCompatibleTypes(typeToMock, mockType, injectMocksField)) { + mockTypeMatches.add(mock); + } // else filter out mock, as generic types don't match + } else { + // field is assignable from mock class, but no generic type information + // is available (can happen with programmatically created Mocks where no + // genericTypeToMock was supplied) + mockTypeMatches.add(mock); + } + } // else filter out mock + // BTW mocks may contain Spy objects with their original class (seemingly before + // being wrapped), and MockUtil.getMockSettings() throws exception for those } - return next.filterCandidate( - mockTypeMatches, candidateFieldToBeInjected, allRemainingCandidateFields, injectee); + boolean wasMultipleMatches = mockTypeMatches.size() > 1; + + OngoingInjector result = + next.filterCandidate( + mockTypeMatches, + candidateFieldToBeInjected, + allRemainingCandidateFields, + injectee, + injectMocksField); + + if (wasMultipleMatches) { + // we had found multiple mocks matching by type, see whether following filters + // were able to reduce this to single match (e.g. by filtering for matching field names) + if (result == OngoingInjector.nop) { + // nope, following filters cannot reduce this to a single match + throw moreThanOneMockCandidate(candidateFieldToBeInjected, mocks); + } + } + return result; } } diff --git a/src/main/java/org/mockito/internal/configuration/plugins/DefaultMockitoPlugins.java b/src/main/java/org/mockito/internal/configuration/plugins/DefaultMockitoPlugins.java index 592f79a81e..365c350e93 100644 --- a/src/main/java/org/mockito/internal/configuration/plugins/DefaultMockitoPlugins.java +++ b/src/main/java/org/mockito/internal/configuration/plugins/DefaultMockitoPlugins.java @@ -5,7 +5,11 @@ package org.mockito.internal.configuration.plugins; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; + +import org.mockito.MockMakers; import org.mockito.plugins.AnnotationEngine; import org.mockito.plugins.DoNotMockEnforcer; import org.mockito.plugins.InstantiatorProvider2; @@ -16,19 +20,23 @@ import org.mockito.plugins.PluginSwitch; import org.mockito.plugins.StackTraceCleanerProvider; -class DefaultMockitoPlugins implements MockitoPlugins { +public class DefaultMockitoPlugins implements MockitoPlugins { private static final Map DEFAULT_PLUGINS = new HashMap<>(); - static final String INLINE_ALIAS = "mock-maker-inline"; - static final String PROXY_ALIAS = "mock-maker-proxy"; + static final String INLINE_ALIAS = MockMakers.INLINE; + static final String PROXY_ALIAS = MockMakers.PROXY; + static final String SUBCLASS_ALIAS = MockMakers.SUBCLASS; + public static final Set MOCK_MAKER_ALIASES = new HashSet<>(); static final String MODULE_ALIAS = "member-accessor-module"; + static final String REFLECTION_ALIAS = "member-accessor-reflection"; + public static final Set MEMBER_ACCESSOR_ALIASES = new HashSet<>(); static { // Keep the mapping: plugin interface name -> plugin implementation class name DEFAULT_PLUGINS.put(PluginSwitch.class.getName(), DefaultPluginSwitch.class.getName()); DEFAULT_PLUGINS.put( MockMaker.class.getName(), - "org.mockito.internal.creation.bytebuddy.ByteBuddyMockMaker"); + "org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker"); DEFAULT_PLUGINS.put( StackTraceCleanerProvider.class.getName(), "org.mockito.internal.exceptions.stacktrace.DefaultStackTraceCleanerProvider"); @@ -41,16 +49,27 @@ class DefaultMockitoPlugins implements MockitoPlugins { DEFAULT_PLUGINS.put( INLINE_ALIAS, "org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker"); DEFAULT_PLUGINS.put(PROXY_ALIAS, "org.mockito.internal.creation.proxy.ProxyMockMaker"); + DEFAULT_PLUGINS.put( + SUBCLASS_ALIAS, "org.mockito.internal.creation.bytebuddy.ByteBuddyMockMaker"); DEFAULT_PLUGINS.put( MockitoLogger.class.getName(), "org.mockito.internal.util.ConsoleMockitoLogger"); DEFAULT_PLUGINS.put( MemberAccessor.class.getName(), - "org.mockito.internal.util.reflection.ReflectionMemberAccessor"); + "org.mockito.internal.util.reflection.ModuleMemberAccessor"); DEFAULT_PLUGINS.put( MODULE_ALIAS, "org.mockito.internal.util.reflection.ModuleMemberAccessor"); + DEFAULT_PLUGINS.put( + REFLECTION_ALIAS, "org.mockito.internal.util.reflection.ReflectionMemberAccessor"); DEFAULT_PLUGINS.put( DoNotMockEnforcer.class.getName(), "org.mockito.internal.configuration.DefaultDoNotMockEnforcer"); + + MOCK_MAKER_ALIASES.add(INLINE_ALIAS); + MOCK_MAKER_ALIASES.add(PROXY_ALIAS); + MOCK_MAKER_ALIASES.add(SUBCLASS_ALIAS); + + MEMBER_ACCESSOR_ALIASES.add(MODULE_ALIAS); + MEMBER_ACCESSOR_ALIASES.add(REFLECTION_ALIAS); } @Override @@ -59,7 +78,7 @@ public T getDefaultPlugin(Class pluginType) { return create(pluginType, className); } - String getDefaultPluginClass(String classOrAlias) { + public static String getDefaultPluginClass(String classOrAlias) { return DEFAULT_PLUGINS.get(classOrAlias); } diff --git a/src/main/java/org/mockito/internal/configuration/plugins/PluginInitializer.java b/src/main/java/org/mockito/internal/configuration/plugins/PluginInitializer.java index a88f0cc19f..b042a84396 100644 --- a/src/main/java/org/mockito/internal/configuration/plugins/PluginInitializer.java +++ b/src/main/java/org/mockito/internal/configuration/plugins/PluginInitializer.java @@ -18,12 +18,10 @@ class PluginInitializer { private final PluginSwitch pluginSwitch; private final Set alias; - private final DefaultMockitoPlugins plugins; - PluginInitializer(PluginSwitch pluginSwitch, Set alias, DefaultMockitoPlugins plugins) { + PluginInitializer(PluginSwitch pluginSwitch, Set alias) { this.pluginSwitch = pluginSwitch; this.alias = alias; - this.plugins = plugins; } /** @@ -47,7 +45,7 @@ public T loadImpl(Class service) { new PluginFinder(pluginSwitch).findPluginClass(Iterables.toIterable(resources)); if (classOrAlias != null) { if (alias.contains(classOrAlias)) { - classOrAlias = plugins.getDefaultPluginClass(classOrAlias); + classOrAlias = DefaultMockitoPlugins.getDefaultPluginClass(classOrAlias); } Class pluginClass = loader.loadClass(classOrAlias); Object plugin = pluginClass.getDeclaredConstructor().newInstance(); @@ -79,7 +77,7 @@ public List loadImpls(Class service) { List impls = new ArrayList<>(); for (String classOrAlias : classesOrAliases) { if (alias.contains(classOrAlias)) { - classOrAlias = plugins.getDefaultPluginClass(classOrAlias); + classOrAlias = DefaultMockitoPlugins.getDefaultPluginClass(classOrAlias); } Class pluginClass = loader.loadClass(classOrAlias); Object plugin = pluginClass.getDeclaredConstructor().newInstance(); diff --git a/src/main/java/org/mockito/internal/configuration/plugins/PluginLoader.java b/src/main/java/org/mockito/internal/configuration/plugins/PluginLoader.java index 3f33dffdc6..e554173326 100644 --- a/src/main/java/org/mockito/internal/configuration/plugins/PluginLoader.java +++ b/src/main/java/org/mockito/internal/configuration/plugins/PluginLoader.java @@ -27,8 +27,7 @@ class PluginLoader { PluginLoader(PluginSwitch pluginSwitch) { this( new DefaultMockitoPlugins(), - new PluginInitializer( - pluginSwitch, Collections.emptySet(), new DefaultMockitoPlugins())); + new PluginInitializer(pluginSwitch, Collections.emptySet())); } /** @@ -40,10 +39,7 @@ class PluginLoader { PluginLoader(PluginSwitch pluginSwitch, String... alias) { this( new DefaultMockitoPlugins(), - new PluginInitializer( - pluginSwitch, - new HashSet<>(Arrays.asList(alias)), - new DefaultMockitoPlugins())); + new PluginInitializer(pluginSwitch, new HashSet<>(Arrays.asList(alias)))); } /** diff --git a/src/main/java/org/mockito/internal/configuration/plugins/PluginRegistry.java b/src/main/java/org/mockito/internal/configuration/plugins/PluginRegistry.java index 01e89d2014..72f5d8e7d5 100644 --- a/src/main/java/org/mockito/internal/configuration/plugins/PluginRegistry.java +++ b/src/main/java/org/mockito/internal/configuration/plugins/PluginRegistry.java @@ -23,12 +23,13 @@ class PluginRegistry { private final MockMaker mockMaker = new PluginLoader( pluginSwitch, - DefaultMockitoPlugins.INLINE_ALIAS, - DefaultMockitoPlugins.PROXY_ALIAS) + DefaultMockitoPlugins.MOCK_MAKER_ALIASES.toArray(new String[0])) .loadPlugin(MockMaker.class); private final MemberAccessor memberAccessor = - new PluginLoader(pluginSwitch, DefaultMockitoPlugins.MODULE_ALIAS) + new PluginLoader( + pluginSwitch, + DefaultMockitoPlugins.MEMBER_ACCESSOR_ALIASES.toArray(new String[0])) .loadPlugin(MemberAccessor.class); private final StackTraceCleanerProvider stackTraceCleanerProvider = diff --git a/src/main/java/org/mockito/internal/creation/MockSettingsImpl.java b/src/main/java/org/mockito/internal/creation/MockSettingsImpl.java index f73a718298..7bef7764d4 100644 --- a/src/main/java/org/mockito/internal/creation/MockSettingsImpl.java +++ b/src/main/java/org/mockito/internal/creation/MockSettingsImpl.java @@ -16,6 +16,7 @@ import static org.mockito.internal.util.collections.Sets.newSet; import java.io.Serializable; +import java.lang.reflect.Type; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -254,11 +255,23 @@ public MockSettings strictness(Strictness strictness) { return this; } + @Override + public MockSettings mockMaker(String mockMaker) { + this.mockMaker = mockMaker; + return this; + } + + @Override + public MockSettings genericTypeToMock(Type genericType) { + this.genericTypeToMock = genericType; + return this; + } + private static CreationSettings validatedSettings( Class typeToMock, CreationSettings source) { MockCreationValidator validator = new MockCreationValidator(); - validator.validateType(typeToMock); + validator.validateType(typeToMock, source.getMockMaker()); validator.validateExtraInterfaces(typeToMock, source.getExtraInterfaces()); validator.validateMockedType(typeToMock, source.getSpiedInstance()); diff --git a/src/main/java/org/mockito/internal/creation/bytebuddy/InlineBytecodeGenerator.java b/src/main/java/org/mockito/internal/creation/bytebuddy/InlineBytecodeGenerator.java index 72ab81adbd..009daa4ccb 100644 --- a/src/main/java/org/mockito/internal/creation/bytebuddy/InlineBytecodeGenerator.java +++ b/src/main/java/org/mockito/internal/creation/bytebuddy/InlineBytecodeGenerator.java @@ -94,8 +94,8 @@ public InlineBytecodeGenerator( .with(Implementation.Context.Disabled.Factory.INSTANCE) .with(MethodGraph.Compiler.ForDeclaredMethods.INSTANCE) .ignore(isSynthetic().and(not(isConstructor())).or(isDefaultFinalizer())); - mocked = new WeakConcurrentSet<>(WeakConcurrentSet.Cleaner.INLINE); - flatMocked = new WeakConcurrentSet<>(WeakConcurrentSet.Cleaner.INLINE); + mocked = new WeakConcurrentSet<>(WeakConcurrentSet.Cleaner.MANUAL); + flatMocked = new WeakConcurrentSet<>(WeakConcurrentSet.Cleaner.MANUAL); String identifier = RandomString.make(); subclassEngine = new TypeCachingBytecodeGenerator( @@ -205,6 +205,7 @@ public Class mockClass(MockFeatures features) { boolean subclassingRequired = !features.interfaces.isEmpty() || features.serializableMode != SerializableMode.NONE + || features.stripAnnotations || Modifier.isAbstract(features.mockedType.getModifiers()); checkSupportedCombination(subclassingRequired, features); @@ -299,6 +300,9 @@ private void triggerRetransformation(Set> types, boolean flat) { lastException = null; } } + + mocked.expungeStaleEntries(); + flatMocked.expungeStaleEntries(); } private void assureCanReadMockito(Set> types) { @@ -413,6 +417,7 @@ public synchronized void clearAllCaches() { } mocked.clear(); flatMocked.clear(); + subclassEngine.clearAllCaches(); try { instrumentation.retransformClasses(types.toArray(new Class[0])); } catch (UnmodifiableClassException e) { diff --git a/src/main/java/org/mockito/internal/creation/bytebuddy/InlineDelegateByteBuddyMockMaker.java b/src/main/java/org/mockito/internal/creation/bytebuddy/InlineDelegateByteBuddyMockMaker.java index 1a19a92732..d40967f94e 100644 --- a/src/main/java/org/mockito/internal/creation/bytebuddy/InlineDelegateByteBuddyMockMaker.java +++ b/src/main/java/org/mockito/internal/creation/bytebuddy/InlineDelegateByteBuddyMockMaker.java @@ -192,13 +192,13 @@ class InlineDelegateByteBuddyMockMaker private final BytecodeGenerator bytecodeGenerator; private final WeakConcurrentMap mocks = - new WeakConcurrentMap.WithInlinedExpunction(); + new WeakConcurrentMap<>(false); private final DetachedThreadLocal, MockMethodInterceptor>> mockedStatics = - new DetachedThreadLocal<>(DetachedThreadLocal.Cleaner.INLINE); + new DetachedThreadLocal<>(DetachedThreadLocal.Cleaner.MANUAL); private final DetachedThreadLocal, BiConsumer>> - mockedConstruction = new DetachedThreadLocal<>(DetachedThreadLocal.Cleaner.INLINE); + mockedConstruction = new DetachedThreadLocal<>(DetachedThreadLocal.Cleaner.MANUAL); private final ThreadLocal mockitoConstruction = ThreadLocal.withInitial(() -> false); @@ -382,6 +382,7 @@ private T doCreateMock( if (instance instanceof MockAccess) { ((MockAccess) instance).setMockitoInterceptor(mockMethodInterceptor); } + mocks.expungeStaleEntries(); return instance; } catch (InstantiationException e) { throw new MockitoException( @@ -446,9 +447,7 @@ private RuntimeException prettifyFailure( "IBM J9 VM", "Early IBM virtual machine are known to have issues with Mockito, please upgrade to an up-to-date version.\n", "Hotspot", - Platform.isJava8BelowUpdate45() - ? "Java 8 early builds have bugs that were addressed in Java 1.8.0_45, please update your JDK!\n" - : ""), + ""), Platform.describe(), "", "You are seeing this disclaimer because Mockito is configured to create inlined mocks.", @@ -496,6 +495,7 @@ public void resetMock(Object mock, MockHandler newHandler, MockCreationSettings if (mock instanceof MockAccess) { ((MockAccess) mock).setMockitoInterceptor(mockMethodInterceptor); } + mocks.expungeStaleEntries(); } } @@ -570,6 +570,7 @@ public StaticMockControl createStaticMock( interceptors = new WeakHashMap<>(); mockedStatics.set(interceptors); } + mockedStatics.getBackingMap().expungeStaleEntries(); return new InlineStaticMockControl<>(type, interceptors, settings, handler); } @@ -598,6 +599,7 @@ public ConstructionMockControl createConstructionMock( interceptors = new WeakHashMap<>(); mockedConstruction.set(interceptors); } + mockedConstruction.getBackingMap().expungeStaleEntries(); return new InlineConstructionMockControl<>( type, settingsFactory, handlerFactory, mockInitializer, interceptors); diff --git a/src/main/java/org/mockito/internal/creation/bytebuddy/MockMethodAdvice.java b/src/main/java/org/mockito/internal/creation/bytebuddy/MockMethodAdvice.java index fc92e49acf..2c3649ea0e 100644 --- a/src/main/java/org/mockito/internal/creation/bytebuddy/MockMethodAdvice.java +++ b/src/main/java/org/mockito/internal/creation/bytebuddy/MockMethodAdvice.java @@ -4,8 +4,8 @@ */ package org.mockito.internal.creation.bytebuddy; +import static net.bytebuddy.matcher.ElementMatchers.isAccessibleTo; import static net.bytebuddy.matcher.ElementMatchers.isConstructor; -import static net.bytebuddy.matcher.ElementMatchers.isPrivate; import static net.bytebuddy.matcher.ElementMatchers.isStatic; import static net.bytebuddy.matcher.ElementMatchers.not; @@ -51,7 +51,7 @@ import org.mockito.exceptions.base.MockitoException; import org.mockito.internal.configuration.plugins.Plugins; import org.mockito.internal.creation.bytebuddy.inject.MockMethodDispatcher; -import org.mockito.internal.debugging.LocationImpl; +import org.mockito.internal.debugging.LocationFactory; import org.mockito.internal.exceptions.stacktrace.ConditionalStackTraceFilter; import org.mockito.internal.invocation.RealMethod; import org.mockito.internal.invocation.SerializableMethod; @@ -132,11 +132,7 @@ public Callable handle(Object instance, Method origin, Object[] arguments) th } return new ReturnValueWrapper( interceptor.doIntercept( - instance, - origin, - arguments, - realMethod, - new LocationImpl(new Throwable(), true))); + instance, origin, arguments, realMethod, LocationFactory.create(true))); } @Override @@ -154,7 +150,7 @@ public Callable handleStatic(Class type, Method origin, Object[] arguments origin, arguments, new StaticMethodCall(selfCallInfo, type, origin, arguments), - new LocationImpl(new Throwable(), true))); + LocationFactory.create(true))); } @Override @@ -172,7 +168,7 @@ public boolean isMock(Object instance) { @Override public boolean isMocked(Object instance) { - return selfCallInfo.checkSelfCall(instance) && isMock(instance); + return isMock(instance) && selfCallInfo.checkSelfCall(instance); } @Override @@ -402,7 +398,7 @@ public MethodVisitor wrap( .getSuperClass() .asErasure() .getDeclaredMethods() - .filter(isConstructor().and(not(isPrivate()))); + .filter(isConstructor().and(isAccessibleTo(instrumentedType))); int arguments = Integer.MAX_VALUE; boolean packagePrivate = true; MethodDescription.InDefinedShape current = null; diff --git a/src/main/java/org/mockito/internal/creation/bytebuddy/MockMethodInterceptor.java b/src/main/java/org/mockito/internal/creation/bytebuddy/MockMethodInterceptor.java index 83908cace6..406dea39a5 100644 --- a/src/main/java/org/mockito/internal/creation/bytebuddy/MockMethodInterceptor.java +++ b/src/main/java/org/mockito/internal/creation/bytebuddy/MockMethodInterceptor.java @@ -22,7 +22,7 @@ import net.bytebuddy.implementation.bind.annotation.StubValue; import net.bytebuddy.implementation.bind.annotation.SuperCall; import net.bytebuddy.implementation.bind.annotation.This; -import org.mockito.internal.debugging.LocationImpl; +import org.mockito.internal.debugging.LocationFactory; import org.mockito.internal.invocation.RealMethod; import org.mockito.invocation.Location; import org.mockito.invocation.MockHandler; @@ -53,7 +53,7 @@ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFo Object doIntercept(Object mock, Method invokedMethod, Object[] arguments, RealMethod realMethod) throws Throwable { - return doIntercept(mock, invokedMethod, arguments, realMethod, new LocationImpl()); + return doIntercept(mock, invokedMethod, arguments, realMethod, LocationFactory.create()); } Object doIntercept( diff --git a/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassByteBuddyMockMaker.java b/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassByteBuddyMockMaker.java index e8dcce229a..6bb74322b3 100644 --- a/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassByteBuddyMockMaker.java +++ b/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassByteBuddyMockMaker.java @@ -122,9 +122,7 @@ private RuntimeException prettifyFailure( "IBM J9 VM", "Early IBM virtual machine are known to have issues with Mockito, please upgrade to an up-to-date version.\n", "Hotspot", - Platform.isJava8BelowUpdate45() - ? "Java 8 early builds have bugs that were addressed in Java 1.8.0_45, please update your JDK!\n" - : ""), + ""), Platform.describe(), "", "Underlying exception : " + generationFailed), diff --git a/src/main/java/org/mockito/internal/creation/instance/ConstructorInstantiator.java b/src/main/java/org/mockito/internal/creation/instance/ConstructorInstantiator.java index c882704caa..30094c58b8 100644 --- a/src/main/java/org/mockito/internal/creation/instance/ConstructorInstantiator.java +++ b/src/main/java/org/mockito/internal/creation/instance/ConstructorInstantiator.java @@ -64,7 +64,8 @@ private T withParams(Class cls, Object... params) { @SuppressWarnings("unchecked") private static T invokeConstructor(Constructor constructor, Object... params) - throws java.lang.InstantiationException, IllegalAccessException, + throws java.lang.InstantiationException, + IllegalAccessException, InvocationTargetException { MemberAccessor accessor = Plugins.getMemberAccessor(); return (T) accessor.newInstance(constructor, params); diff --git a/src/main/java/org/mockito/internal/creation/proxy/ProxyMockMaker.java b/src/main/java/org/mockito/internal/creation/proxy/ProxyMockMaker.java index faa97e849b..88e6886111 100644 --- a/src/main/java/org/mockito/internal/creation/proxy/ProxyMockMaker.java +++ b/src/main/java/org/mockito/internal/creation/proxy/ProxyMockMaker.java @@ -5,7 +5,7 @@ package org.mockito.internal.creation.proxy; import org.mockito.exceptions.base.MockitoException; -import org.mockito.internal.debugging.LocationImpl; +import org.mockito.internal.debugging.LocationFactory; import org.mockito.internal.invocation.RealMethod; import org.mockito.internal.util.Platform; import org.mockito.invocation.MockHandler; @@ -153,7 +153,12 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl return handler.get() .handle( createInvocation( - proxy, method, args, realMethod, settings, new LocationImpl())); + proxy, + method, + args, + realMethod, + settings, + LocationFactory.create())); } } diff --git a/src/main/java/org/mockito/internal/creation/settings/CreationSettings.java b/src/main/java/org/mockito/internal/creation/settings/CreationSettings.java index 13939f6fbd..9114bcb8cf 100644 --- a/src/main/java/org/mockito/internal/creation/settings/CreationSettings.java +++ b/src/main/java/org/mockito/internal/creation/settings/CreationSettings.java @@ -5,6 +5,7 @@ package org.mockito.internal.creation.settings; import java.io.Serializable; +import java.lang.reflect.Type; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.LinkedList; @@ -25,6 +26,7 @@ public class CreationSettings implements MockCreationSettings, Serializabl private static final long serialVersionUID = -6789800638070123629L; protected Class typeToMock; + protected Type genericTypeToMock; protected Set> extraInterfaces = new LinkedHashSet<>(); protected String name; protected Object spiedInstance; @@ -46,6 +48,7 @@ public class CreationSettings implements MockCreationSettings, Serializabl private Object outerClassInstance; private Object[] constructorArgs; protected Strictness strictness = null; + protected String mockMaker; public CreationSettings() {} @@ -53,6 +56,7 @@ public CreationSettings() {} public CreationSettings(CreationSettings copy) { // TODO can we have a reflection test here? We had a couple of bugs here in the past. this.typeToMock = copy.typeToMock; + this.genericTypeToMock = copy.genericTypeToMock; this.extraInterfaces = copy.extraInterfaces; this.name = copy.name; this.spiedInstance = copy.spiedInstance; @@ -68,6 +72,7 @@ public CreationSettings(CreationSettings copy) { this.constructorArgs = copy.getConstructorArgs(); this.strictness = copy.strictness; this.stripAnnotations = copy.stripAnnotations; + this.mockMaker = copy.mockMaker; } @Override @@ -80,6 +85,11 @@ public CreationSettings setTypeToMock(Class typeToMock) { return this; } + public CreationSettings setGenericTypeToMock(Type genericTypeToMock) { + this.genericTypeToMock = genericTypeToMock; + return this; + } + @Override public Set> getExtraInterfaces() { return extraInterfaces; @@ -178,4 +188,14 @@ public boolean isLenient() { public Strictness getStrictness() { return strictness; } + + @Override + public String getMockMaker() { + return mockMaker; + } + + @Override + public Type getGenericTypeToMock() { + return genericTypeToMock; + } } diff --git a/src/main/java/org/mockito/internal/debugging/Java8LocationImpl.java b/src/main/java/org/mockito/internal/debugging/Java8LocationImpl.java new file mode 100644 index 0000000000..e8ee387c0a --- /dev/null +++ b/src/main/java/org/mockito/internal/debugging/Java8LocationImpl.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.internal.debugging; + +import java.io.Serializable; + +import org.mockito.internal.exceptions.stacktrace.StackTraceFilter; +import org.mockito.invocation.Location; + +class Java8LocationImpl implements Location, Serializable { + + private static final long serialVersionUID = -9054861157390980624L; + // Limit the amount of objects being created, as this class is heavily instantiated: + private static final StackTraceFilter stackTraceFilter = new StackTraceFilter(); + + private String stackTraceLine; + private String sourceFile; + + public Java8LocationImpl(Throwable stackTraceHolder, boolean isInline) { + this(stackTraceFilter, stackTraceHolder, isInline); + } + + private Java8LocationImpl( + StackTraceFilter stackTraceFilter, Throwable stackTraceHolder, boolean isInline) { + computeStackTraceInformation(stackTraceFilter, stackTraceHolder, isInline); + } + + @Override + public String toString() { + return stackTraceLine; + } + + /** + * Eagerly compute the stacktrace line from the stackTraceHolder. Storing the Throwable is + * memory-intensive for tests that have large stacktraces and have a lot of invocations on + * mocks. + */ + private void computeStackTraceInformation( + StackTraceFilter stackTraceFilter, Throwable stackTraceHolder, boolean isInline) { + StackTraceElement filtered = stackTraceFilter.filterFirst(stackTraceHolder, isInline); + + // there are corner cases where exception can have a null or empty stack trace + // for example, a custom exception can override getStackTrace() method + if (filtered == null) { + this.stackTraceLine = "-> at <>"; + this.sourceFile = ""; + } else { + this.stackTraceLine = "-> at " + filtered; + this.sourceFile = filtered.getFileName(); + } + } + + @Override + public String getSourceFile() { + return sourceFile; + } +} diff --git a/src/main/java/org/mockito/internal/debugging/Localized.java b/src/main/java/org/mockito/internal/debugging/Localized.java index d1d7912dc4..3abcf29555 100644 --- a/src/main/java/org/mockito/internal/debugging/Localized.java +++ b/src/main/java/org/mockito/internal/debugging/Localized.java @@ -13,7 +13,7 @@ public class Localized { public Localized(T object) { this.object = object; - location = new LocationImpl(); + location = LocationFactory.create(); } public T getObject() { diff --git a/src/main/java/org/mockito/internal/debugging/LocationFactory.java b/src/main/java/org/mockito/internal/debugging/LocationFactory.java new file mode 100644 index 0000000000..3d936ae761 --- /dev/null +++ b/src/main/java/org/mockito/internal/debugging/LocationFactory.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.internal.debugging; + +import org.mockito.invocation.Location; + +public final class LocationFactory { + private static final Factory factory = createLocationFactory(); + + private LocationFactory() {} + + public static Location create() { + return create(false); + } + + public static Location create(boolean inline) { + return factory.create(inline); + } + + private interface Factory { + Location create(boolean inline); + } + + private static Factory createLocationFactory() { + try { + // On some platforms, like Android, the StackWalker APIs may not be + // available, in this case we have to fallback to Java 8 style of stack + // traversing. + Class.forName("java.lang.StackWalker"); + return new DefaultLocationFactory(); + } catch (ClassNotFoundException e) { + return new Java8LocationFactory(); + } + } + + private static final class Java8LocationFactory implements Factory { + @Override + public Location create(boolean inline) { + return new Java8LocationImpl(new Throwable(), inline); + } + } + + private static final class DefaultLocationFactory implements Factory { + @Override + public Location create(boolean inline) { + return new LocationImpl(inline); + } + } +} diff --git a/src/main/java/org/mockito/internal/debugging/LocationImpl.java b/src/main/java/org/mockito/internal/debugging/LocationImpl.java index cf255013e2..ea72eeb7fd 100644 --- a/src/main/java/org/mockito/internal/debugging/LocationImpl.java +++ b/src/main/java/org/mockito/internal/debugging/LocationImpl.java @@ -4,64 +4,208 @@ */ package org.mockito.internal.debugging; +import org.mockito.exceptions.base.MockitoException; +import org.mockito.exceptions.stacktrace.StackTraceCleaner; +import org.mockito.exceptions.stacktrace.StackTraceCleaner.StackFrameMetadata; +import org.mockito.internal.configuration.plugins.Plugins; +import org.mockito.internal.exceptions.stacktrace.DefaultStackTraceCleaner; +import org.mockito.invocation.Location; + import java.io.Serializable; +import java.lang.StackWalker.Option; +import java.lang.StackWalker.StackFrame; +import java.util.Collections; +import java.util.List; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; -import org.mockito.internal.exceptions.stacktrace.StackTraceFilter; -import org.mockito.invocation.Location; +class LocationImpl implements Location, Serializable { + private static final long serialVersionUID = 2954388321980069195L; -public class LocationImpl implements Location, Serializable { + private static final String UNEXPECTED_ERROR_SUFFIX = + "\nThis is unexpected and is likely due to a change in either Java's StackWalker or Reflection APIs." + + "\nIt's worth trying to upgrade to a newer version of Mockito, or otherwise to file a bug report."; - private static final long serialVersionUID = -9054861157390980624L; - // Limit the amount of objects being created, as this class is heavily instantiated: - private static final StackTraceFilter stackTraceFilter = new StackTraceFilter(); + /** + * This is an unfortunate buffer. Inside StackWalker, a buffer is created, which is resized by + * doubling. The resizing also allocates a tonne of StackFrame elements. If we traverse more than + * BUFFER_SIZE elements, the resulting resize can significantly affect the overall cost of the operation. + * If we traverse fewer than this number, we are inefficient. Empirically, 16 is enough stack frames + * for a simple stub+call operation to succeed without resizing, as measured on Java 11. + */ + private static final int BUFFER_SIZE = 16; - private String stackTraceLine; - private String sourceFile; + private static final StackWalker STACK_WALKER = stackWalker(); - public LocationImpl() { - this(new Throwable(), false); - } + private static final String PREFIX = "-> at "; - public LocationImpl(Throwable stackTraceHolder, boolean isInline) { - this(stackTraceFilter, stackTraceHolder, isInline); - } + private static final StackTraceCleaner CLEANER = + Plugins.getStackTraceCleanerProvider() + .getStackTraceCleaner(new DefaultStackTraceCleaner()); - public LocationImpl(StackTraceFilter stackTraceFilter) { - this(stackTraceFilter, new Throwable(), false); + /** + * In Java, allocating lambdas is cheap, but not free. stream.map(this::doSomething) + * will allocate a Function object each time the function is called (although not + * per element). By assigning these Functions and Predicates to variables, we can + * avoid the memory allocation. + */ + private static final Function toStackFrameMetadata = + MetadataShim::new; + + private static final Predicate cleanerIsIn = CLEANER::isIn; + + private static final int FRAMES_TO_SKIP = framesToSkip(); + + private final StackFrameMetadata sfm; + private volatile String stackTraceLine; + + LocationImpl(boolean isInline) { + this.sfm = getStackFrame(isInline); } - private LocationImpl( - StackTraceFilter stackTraceFilter, Throwable stackTraceHolder, boolean isInline) { - computeStackTraceInformation(stackTraceFilter, stackTraceHolder, isInline); + @Override + public String getSourceFile() { + return sfm.getFileName(); } @Override public String toString() { + return stackTraceLine(); + } + + private String stackTraceLine() { + if (stackTraceLine == null) { + synchronized (this) { + if (stackTraceLine == null) { + stackTraceLine = PREFIX + sfm.toString(); + } + } + } return stackTraceLine; } + private static StackFrameMetadata getStackFrame(boolean isInline) { + return stackWalk( + stream -> + stream.map(toStackFrameMetadata) + .skip(FRAMES_TO_SKIP) + .filter(cleanerIsIn) + .skip(isInline ? 1 : 0) + .findFirst() + .orElseThrow( + () -> new MockitoException(noStackTraceFailureMessage()))); + } + + private static boolean usingDefaultStackTraceCleaner() { + return CLEANER instanceof DefaultStackTraceCleaner; + } + + private static String noStackTraceFailureMessage() { + if (usingDefaultStackTraceCleaner()) { + return "Mockito could not find the first non-Mockito stack frame." + + UNEXPECTED_ERROR_SUFFIX; + } else { + String cleanerType = CLEANER.getClass().getName(); + String fmt = + "Mockito could not find the first non-Mockito stack frame. A custom stack frame cleaner \n" + + "(type %s) is in use and this has mostly likely filtered out all the relevant stack frames."; + return String.format(fmt, cleanerType); + } + } + /** - * Eagerly compute the stacktrace line from the stackTraceHolder. Storing the Throwable is - * memory-intensive for tests that have large stacktraces and have a lot of invocations on - * mocks. + * In order to trigger the stack walker, we create some reflective frames. These need to be skipped so as to + * ensure there are no non-Mockito frames at the top of the stack trace. */ - private void computeStackTraceInformation( - StackTraceFilter stackTraceFilter, Throwable stackTraceHolder, boolean isInline) { - StackTraceElement filtered = stackTraceFilter.filterFirst(stackTraceHolder, isInline); - - // there are corner cases where exception can have a null or empty stack trace - // for example, a custom exception can override getStackTrace() method - if (filtered == null) { - this.stackTraceLine = "-> at <>"; - this.sourceFile = ""; - } else { - this.stackTraceLine = "-> at " + filtered; - this.sourceFile = filtered.getFileName(); + private static int framesToSkip() { + return stackWalk( + stream -> { + List metadata = + stream.map(toStackFrameMetadata) + .map(StackFrameMetadata::getClassName) + .collect(Collectors.toList()); + return metadata.indexOf(LocationImpl.class.getName()); + }); + } + + private static T stackWalk(Function, T> function) { + return (T) STACK_WALKER.walk(function); + } + + private static StackWalker stackWalker() { + return StackWalker.getInstance( + Collections.singleton(Option.SHOW_REFLECT_FRAMES), BUFFER_SIZE); + } + + private static final class MetadataShim implements StackFrameMetadata, Serializable { + private static final long serialVersionUID = 8491903719411428648L; + private final StackFrame stackFrame; + + private MetadataShim(StackFrame stackFrame) { + this.stackFrame = stackFrame; + } + + @Override + public String getClassName() { + return stackFrame.getClassName(); + } + + @Override + public String getMethodName() { + return stackFrame.getMethodName(); + } + + @Override + public String getFileName() { + return stackFrame.getFileName(); + } + + @Override + public int getLineNumber() { + return stackFrame.getLineNumber(); + } + + @Override + public String toString() { + return stackFrame.toString(); + } + + /** + * Ensure that this type remains serializable. + */ + private Object writeReplace() { + return new SerializableShim(stackFrame.toStackTraceElement()); } } - @Override - public String getSourceFile() { - return sourceFile; + private static final class SerializableShim implements StackFrameMetadata, Serializable { + private static final long serialVersionUID = 7908320459080898690L; + private final StackTraceElement ste; + + private SerializableShim(StackTraceElement ste) { + this.ste = ste; + } + + @Override + public String getClassName() { + return ste.getClassName(); + } + + @Override + public String getMethodName() { + return ste.getMethodName(); + } + + @Override + public String getFileName() { + return ste.getFileName(); + } + + @Override + public int getLineNumber() { + return ste.getLineNumber(); + } } } diff --git a/src/main/java/org/mockito/internal/debugging/LoggingListener.java b/src/main/java/org/mockito/internal/debugging/LoggingListener.java index 6e0a645ce1..c5ebee4aa5 100644 --- a/src/main/java/org/mockito/internal/debugging/LoggingListener.java +++ b/src/main/java/org/mockito/internal/debugging/LoggingListener.java @@ -82,7 +82,7 @@ public String getStubbingInfo() { if (!unstubbedCalls.isEmpty()) { lines.add("[Mockito]"); lines.add( - "[Mockito] Unstubbed method invocations (perhaps missing stubbing in the test?):"); + "[Mockito] Un-stubbed method invocations (perhaps missing stubbing in the test?):"); lines.add("[Mockito]"); addOrderedList(lines, unstubbedCalls); } diff --git a/src/main/java/org/mockito/internal/exceptions/Reporter.java b/src/main/java/org/mockito/internal/exceptions/Reporter.java index ea670407e7..0609168465 100644 --- a/src/main/java/org/mockito/internal/exceptions/Reporter.java +++ b/src/main/java/org/mockito/internal/exceptions/Reporter.java @@ -10,11 +10,30 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + import org.mockito.exceptions.base.MockitoAssertionError; import org.mockito.exceptions.base.MockitoException; import org.mockito.exceptions.base.MockitoInitializationException; -import org.mockito.exceptions.misusing.*; +import org.mockito.exceptions.misusing.CannotStubVoidMethodWithReturnValue; +import org.mockito.exceptions.misusing.CannotVerifyStubOnlyMock; +import org.mockito.exceptions.misusing.FriendlyReminderException; +import org.mockito.exceptions.misusing.InjectMocksException; +import org.mockito.exceptions.misusing.InvalidUseOfMatchersException; +import org.mockito.exceptions.misusing.MissingMethodInvocationException; +import org.mockito.exceptions.misusing.NotAMockException; +import org.mockito.exceptions.misusing.NullInsteadOfMockException; +import org.mockito.exceptions.misusing.PotentialStubbingProblem; +import org.mockito.exceptions.misusing.RedundantListenerException; +import org.mockito.exceptions.misusing.UnfinishedMockingSessionException; +import org.mockito.exceptions.misusing.UnfinishedStubbingException; +import org.mockito.exceptions.misusing.UnfinishedVerificationException; +import org.mockito.exceptions.misusing.UnnecessaryStubbingException; +import org.mockito.exceptions.misusing.WrongTypeOfReturnValue; import org.mockito.exceptions.verification.MoreThanAllowedActualInvocations; import org.mockito.exceptions.verification.NeverWantedButInvoked; import org.mockito.exceptions.verification.NoInteractionsWanted; @@ -23,16 +42,19 @@ import org.mockito.exceptions.verification.TooManyActualInvocations; import org.mockito.exceptions.verification.VerificationInOrderFailure; import org.mockito.exceptions.verification.WantedButNotInvoked; -import org.mockito.internal.debugging.LocationImpl; +import org.mockito.internal.debugging.LocationFactory; import org.mockito.internal.exceptions.util.ScenarioPrinter; import org.mockito.internal.junit.ExceptionFactory; import org.mockito.internal.matchers.LocalizedMatcher; import org.mockito.internal.util.MockUtil; +import org.mockito.internal.verification.argumentmatching.ArgumentMatchingTool; import org.mockito.invocation.DescribedInvocation; import org.mockito.invocation.Invocation; import org.mockito.invocation.InvocationOnMock; import org.mockito.invocation.Location; +import org.mockito.invocation.MatchableInvocation; import org.mockito.listeners.InvocationListener; +import org.mockito.mock.MockName; import org.mockito.mock.SerializableMode; /** @@ -84,7 +106,7 @@ public static MockitoException incorrectUseOfApi() { return new MockitoException( join( "Incorrect use of API detected here:", - new LocationImpl(), + LocationFactory.create(), "", "You probably stored a reference to OngoingStubbing returned by when() and called stubbing methods like thenReturn() on this reference more than once.", "Examples of correct usage:", @@ -262,7 +284,7 @@ public static MockitoException incorrectUseOfAdditionalMatchers( "Invalid use of argument matchers inside additional matcher " + additionalMatcherName + " !", - new LocationImpl(), + LocationFactory.create(), "", expectedSubMatchersCount + " sub matchers expected, " @@ -295,7 +317,7 @@ public static MockitoException reportNoSubMatchersFound(String additionalMatcher return new InvalidUseOfMatchersException( join( "No matchers found for additional matcher " + additionalMatcherName, - new LocationImpl(), + LocationFactory.create(), "")); } @@ -308,7 +330,11 @@ private static Object locationsOf(Collection matchers) { } public static AssertionError argumentsAreDifferent( - String wanted, List actualCalls, List actualLocations) { + Invocation actualInvocation, + MatchableInvocation matchableInvocation, + String wanted, + List actualCalls, + List actualLocations) { if (actualCalls == null || actualLocations == null || actualCalls.size() != actualLocations.size()) { @@ -322,9 +348,13 @@ public static AssertionError argumentsAreDifferent( .append("Argument(s) are different! Wanted:\n") .append(wanted) .append("\n") - .append(new LocationImpl()) + .append(LocationFactory.create()) .append("\n") - .append("Actual invocations have different arguments:\n"); + .append("Actual invocations have different arguments"); + + appendNotMatchingPositions(actualInvocation, matchableInvocation, messageBuilder); + + messageBuilder.append(":\n"); for (int i = 0; i < actualCalls.size(); i++) { actualBuilder.append(actualCalls.get(i)).append("\n"); @@ -340,6 +370,30 @@ public static AssertionError argumentsAreDifferent( messageBuilder.toString(), wanted, actualBuilder.toString()); } + /* + * Will append the non matching positions only if there are more than 1 arguments to the method. + */ + private static void appendNotMatchingPositions( + Invocation actualInvocation, + MatchableInvocation matchableInvocation, + StringBuilder messageBuilder) { + Object[] args = actualInvocation.getArguments(); + if (args.length <= 1) { + return; + } + + List indexes = + ArgumentMatchingTool.getNotMatchingArgsIndexes( + matchableInvocation.getMatchers(), actualInvocation.getArguments()); + + if (!indexes.isEmpty()) { + messageBuilder + .append(" at ") + .append(indexes.size() == 1 ? "position " : "positions ") + .append(indexes); + } + } + public static MockitoAssertionError wantedButNotInvoked(DescribedInvocation wanted) { return new WantedButNotInvoked(createWantedButNotInvokedMessage(wanted)); } @@ -365,7 +419,7 @@ public static MockitoAssertionError wantedButNotInvoked( } private static String createWantedButNotInvokedMessage(DescribedInvocation wanted) { - return join("Wanted but not invoked:", wanted.toString(), new LocationImpl(), ""); + return join("Wanted but not invoked:", wanted.toString(), LocationFactory.create(), ""); } public static MockitoAssertionError wantedButNotInvokedInOrder( @@ -375,7 +429,7 @@ public static MockitoAssertionError wantedButNotInvokedInOrder( "Verification in order failure", "Wanted but not invoked:", wanted.toString(), - new LocationImpl(), + LocationFactory.create(), "Wanted anywhere AFTER following interaction:", previous.toString(), previous.getLocation(), @@ -400,7 +454,7 @@ private static String createTooManyInvocationsMessage( return join( wanted.toString(), "Wanted " + pluralize(wantedCount) + ":", - new LocationImpl(), + LocationFactory.create(), "But was " + pluralize(actualCount) + ":", createAllLocationsMessage(invocations), ""); @@ -413,7 +467,7 @@ public static MockitoAssertionError neverWantedButInvoked( join( wanted.toString(), "Never wanted here:", - new LocationImpl(), + LocationFactory.create(), "But invoked here:", createAllLocationsArgsMessage(invocations))); } @@ -463,7 +517,7 @@ private static String createTooFewInvocationsMessage( "Wanted " + discrepancy.getPluralizedWantedCount() + (discrepancy.getWantedCount() == 0 ? "." : ":"), - new LocationImpl(), + LocationFactory.create(), "But was " + discrepancy.getPluralizedActualCount() + (discrepancy.getActualCount() == 0 ? "." : ":"), @@ -496,7 +550,7 @@ public static MockitoAssertionError noMoreInteractionsWanted( return new NoInteractionsWanted( join( "No interactions wanted here:", - new LocationImpl(), + LocationFactory.create(), "But found this interaction on mock '" + MockUtil.getMockName(undesired.getMock()) + "':", @@ -508,7 +562,7 @@ public static MockitoAssertionError noMoreInteractionsWantedInOrder(Invocation u return new VerificationInOrderFailure( join( "No interactions wanted here:", - new LocationImpl(), + LocationFactory.create(), "But found this interaction on mock '" + MockUtil.getMockName(undesired.getMock()) + "':", @@ -527,7 +581,7 @@ public static MockitoAssertionError noInteractionsWanted( return new NoInteractionsWanted( join( "No interactions wanted here:", - new LocationImpl(), + LocationFactory.create(), "But found these interactions on mock '" + MockUtil.getMockName(mock) + "':", @@ -645,7 +699,7 @@ public static MockitoException smartNullPointerException(String invocation, Loca return new SmartNullPointerException( join( "You have a NullPointerException here:", - new LocationImpl(), + LocationFactory.create(), "because this method call was *not* stubbed correctly:", location, invocation, @@ -841,6 +895,29 @@ public static MockitoException cannotInjectDependency( details); } + public static MockitoException moreThanOneMockCandidate( + Field field, Collection mockCandidates) { + List mockNames = + mockCandidates.stream() + .map(MockUtil::getMockName) + .map(MockName::toString) + .collect(Collectors.toList()); + return new MockitoException( + join( + "Mockito couldn't inject mock dependency on field " + + "'" + + field + + "' that is annotated with @InjectMocks in your test, ", + "because there were multiple matching mocks (i.e. " + + "fields annotated with @Mock and having matching type): " + + String.join(", ", mockNames) + + ".", + "If you have multiple fields of same type in your class under test " + + "then consider naming the @Mock fields " + + "identically to the respective class under test's fields, " + + "so Mockito can match them by name.")); + } + private static String exceptionCauseMessageIfAvailable(Exception details) { if (details.getCause() == null) { return details.getMessage(); diff --git a/src/main/java/org/mockito/internal/exceptions/stacktrace/DefaultStackTraceCleaner.java b/src/main/java/org/mockito/internal/exceptions/stacktrace/DefaultStackTraceCleaner.java index 6b0575273e..797515dea1 100644 --- a/src/main/java/org/mockito/internal/exceptions/stacktrace/DefaultStackTraceCleaner.java +++ b/src/main/java/org/mockito/internal/exceptions/stacktrace/DefaultStackTraceCleaner.java @@ -13,15 +13,31 @@ public class DefaultStackTraceCleaner implements StackTraceCleaner { @Override public boolean isIn(StackTraceElement e) { - if (isFromMockitoRunner(e.getClassName()) || isFromMockitoRule(e.getClassName())) { + return isIn(e.getClassName()); + } + + @Override + public boolean isIn(StackFrameMetadata e) { + return isIn(e.getClassName()); + } + + private boolean isIn(String className) { + if (isFromMockitoRunner(className) || isFromMockitoRule(className)) { return true; - } else if (isMockDispatcher(e.getClassName()) || isFromMockito(e.getClassName())) { + } else if (isMockDispatcher(className) + || isFromMockito(className) + || isMethodHandle(className)) { return false; } else { return true; } } + /* Some mock makers (like inline) use java.lang.invoke.MethodHandle to dispatch calls */ + private static boolean isMethodHandle(String className) { + return className.startsWith("java.lang.invoke.MethodHandle"); + } + private static boolean isMockDispatcher(String className) { return (className.contains("$$EnhancerByMockitoWithCGLIB$$") || className.contains("$MockitoMock$")); diff --git a/src/main/java/org/mockito/internal/hamcrest/HamcrestArgumentMatcher.java b/src/main/java/org/mockito/internal/hamcrest/HamcrestArgumentMatcher.java index 99869faf73..f9ef55d528 100644 --- a/src/main/java/org/mockito/internal/hamcrest/HamcrestArgumentMatcher.java +++ b/src/main/java/org/mockito/internal/hamcrest/HamcrestArgumentMatcher.java @@ -7,14 +7,25 @@ import org.hamcrest.Matcher; import org.hamcrest.StringDescription; import org.mockito.ArgumentMatcher; -import org.mockito.internal.matchers.VarargMatcher; + +import static java.util.Objects.requireNonNull; public class HamcrestArgumentMatcher implements ArgumentMatcher { - private final Matcher matcher; + private final Matcher matcher; + private final Class type; public HamcrestArgumentMatcher(Matcher matcher) { - this.matcher = matcher; + this(Void.class, matcher); + } + + public HamcrestArgumentMatcher(Matcher matcher, Class type) { + this(type, matcher); + } + + private HamcrestArgumentMatcher(Class type, Matcher matcher) { + this.type = requireNonNull(type, "type"); + this.matcher = requireNonNull(matcher, "matcher"); } @Override @@ -22,13 +33,14 @@ public boolean matches(Object argument) { return this.matcher.matches(argument); } - public boolean isVarargMatcher() { - return matcher instanceof VarargMatcher; - } - @Override public String toString() { // TODO SF add unit tests and integ test coverage for toString() return StringDescription.toString(matcher); } + + @Override + public Class type() { + return type; + } } diff --git a/src/main/java/org/mockito/internal/invocation/DefaultInvocationFactory.java b/src/main/java/org/mockito/internal/invocation/DefaultInvocationFactory.java index 81f801518f..4921f4006d 100644 --- a/src/main/java/org/mockito/internal/invocation/DefaultInvocationFactory.java +++ b/src/main/java/org/mockito/internal/invocation/DefaultInvocationFactory.java @@ -8,7 +8,7 @@ import java.util.concurrent.Callable; import org.mockito.internal.creation.DelegatingMethod; -import org.mockito.internal.debugging.LocationImpl; +import org.mockito.internal.debugging.LocationFactory; import org.mockito.internal.invocation.mockref.MockWeakReference; import org.mockito.internal.progress.SequenceNumber; import org.mockito.invocation.Invocation; @@ -71,7 +71,7 @@ private static InterceptedInvocation createInvocation( RealMethod realMethod, MockCreationSettings settings) { return createInvocation( - mock, invokedMethod, arguments, realMethod, settings, new LocationImpl()); + mock, invokedMethod, arguments, realMethod, settings, LocationFactory.create()); } private static MockitoMethod createMockitoMethod(Method method, MockCreationSettings settings) { diff --git a/src/main/java/org/mockito/internal/invocation/MatcherApplicationStrategy.java b/src/main/java/org/mockito/internal/invocation/MatcherApplicationStrategy.java index acc7382e2c..8b50da635d 100644 --- a/src/main/java/org/mockito/internal/invocation/MatcherApplicationStrategy.java +++ b/src/main/java/org/mockito/internal/invocation/MatcherApplicationStrategy.java @@ -4,38 +4,21 @@ */ package org.mockito.internal.invocation; -import static org.mockito.internal.invocation.MatcherApplicationStrategy.MatcherApplicationType.ERROR_UNSUPPORTED_NUMBER_OF_MATCHERS; -import static org.mockito.internal.invocation.MatcherApplicationStrategy.MatcherApplicationType.MATCH_EACH_VARARGS_WITH_LAST_MATCHER; -import static org.mockito.internal.invocation.MatcherApplicationStrategy.MatcherApplicationType.ONE_MATCHER_PER_ARGUMENT; - -import java.util.ArrayList; import java.util.List; import org.mockito.ArgumentMatcher; -import org.mockito.internal.hamcrest.HamcrestArgumentMatcher; import org.mockito.internal.matchers.CapturingMatcher; -import org.mockito.internal.matchers.VarargMatcher; import org.mockito.invocation.Invocation; public class MatcherApplicationStrategy { private final Invocation invocation; - private final List> matchers; - private final MatcherApplicationType matchingType; + private final List> matchers; private MatcherApplicationStrategy( - Invocation invocation, - List> matchers, - MatcherApplicationType matchingType) { + Invocation invocation, List> matchers) { this.invocation = invocation; - if (matchingType == MATCH_EACH_VARARGS_WITH_LAST_MATCHER) { - int times = varargLength(invocation); - this.matchers = appendLastMatcherNTimes(matchers, times); - } else { - this.matchers = matchers; - } - - this.matchingType = matchingType; + this.matchers = matchers; } /** @@ -51,10 +34,8 @@ private MatcherApplicationStrategy( * @return never null */ public static MatcherApplicationStrategy getMatcherApplicationStrategyFor( - Invocation invocation, List> matchers) { - - MatcherApplicationType type = getMatcherApplicationType(invocation, matchers); - return new MatcherApplicationStrategy(invocation, matchers, type); + Invocation invocation, List> matchers) { + return new MatcherApplicationStrategy(invocation, matchers); } /** @@ -74,11 +55,29 @@ public static MatcherApplicationStrategy getMatcherApplicationStrategyFor( * */ public boolean forEachMatcherAndArgument(ArgumentMatcherAction action) { - if (matchingType == ERROR_UNSUPPORTED_NUMBER_OF_MATCHERS) { - return false; + final boolean maybeVararg = + invocation.getMethod().isVarArgs() + && invocation.getRawArguments().length == matchers.size(); + + if (maybeVararg) { + final Class matcherType = lastMatcherType(); + final Class paramType = lastParameterType(); + if (paramType.isAssignableFrom(matcherType)) { + return argsMatch(invocation.getRawArguments(), matchers, action); + } + } + + if (invocation.getArguments().length == matchers.size()) { + return argsMatch(invocation.getArguments(), matchers, action); } - Object[] arguments = invocation.getArguments(); + return false; + } + + private boolean argsMatch( + Object[] arguments, + List> matchers, + ArgumentMatcherAction action) { for (int i = 0; i < arguments.length; i++) { ArgumentMatcher matcher = matchers.get(i); Object argument = arguments[i]; @@ -90,55 +89,12 @@ public boolean forEachMatcherAndArgument(ArgumentMatcherAction action) { return true; } - private static MatcherApplicationType getMatcherApplicationType( - Invocation invocation, List> matchers) { - final int rawArguments = invocation.getRawArguments().length; - final int expandedArguments = invocation.getArguments().length; - final int matcherCount = matchers.size(); - - if (expandedArguments == matcherCount) { - return ONE_MATCHER_PER_ARGUMENT; - } - - if (rawArguments == matcherCount && isLastMatcherVarargMatcher(matchers)) { - return MATCH_EACH_VARARGS_WITH_LAST_MATCHER; - } - - return ERROR_UNSUPPORTED_NUMBER_OF_MATCHERS; - } - - private static boolean isLastMatcherVarargMatcher(final List> matchers) { - ArgumentMatcher argumentMatcher = lastMatcher(matchers); - if (argumentMatcher instanceof HamcrestArgumentMatcher) { - return ((HamcrestArgumentMatcher) argumentMatcher).isVarargMatcher(); - } - return argumentMatcher instanceof VarargMatcher; - } - - private static List> appendLastMatcherNTimes( - List> matchers, int timesToAppendLastMatcher) { - ArgumentMatcher lastMatcher = lastMatcher(matchers); - - List> expandedMatchers = new ArrayList>(matchers); - for (int i = 0; i < timesToAppendLastMatcher; i++) { - expandedMatchers.add(lastMatcher); - } - return expandedMatchers; - } - - private static int varargLength(Invocation invocation) { - int rawArgumentCount = invocation.getRawArguments().length; - int expandedArgumentCount = invocation.getArguments().length; - return expandedArgumentCount - rawArgumentCount; - } - - private static ArgumentMatcher lastMatcher(List> matchers) { - return matchers.get(matchers.size() - 1); + private Class lastMatcherType() { + return matchers.get(matchers.size() - 1).type(); } - enum MatcherApplicationType { - ONE_MATCHER_PER_ARGUMENT, - MATCH_EACH_VARARGS_WITH_LAST_MATCHER, - ERROR_UNSUPPORTED_NUMBER_OF_MATCHERS; + private Class lastParameterType() { + final Class[] parameterTypes = invocation.getMethod().getParameterTypes(); + return parameterTypes[parameterTypes.length - 1]; } } diff --git a/src/main/java/org/mockito/internal/invocation/TypeSafeMatching.java b/src/main/java/org/mockito/internal/invocation/TypeSafeMatching.java index b4ca07aca6..8f8af6dbdb 100644 --- a/src/main/java/org/mockito/internal/invocation/TypeSafeMatching.java +++ b/src/main/java/org/mockito/internal/invocation/TypeSafeMatching.java @@ -4,15 +4,26 @@ */ package org.mockito.internal.invocation; -import java.lang.reflect.Method; - import org.mockito.ArgumentMatcher; +import java.lang.reflect.Method; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + @SuppressWarnings({"unchecked", "rawtypes"}) public class TypeSafeMatching implements ArgumentMatcherAction { private static final ArgumentMatcherAction TYPE_SAFE_MATCHING_ACTION = new TypeSafeMatching(); + /** + * This cache is in theory unbounded. However, its max size is bounded by the number of types of argument matchers + * that are both in the system and being used, which is expected to bound the cache's size to a low number + * (<200) in all but the most contrived cases, and form a small percentage of the overall memory usage of those + * classes. + */ + private static final ConcurrentMap, Class> argumentTypeCache = + new ConcurrentHashMap<>(); + private TypeSafeMatching() {} public static ArgumentMatcherAction matchesTypeSafe() { @@ -39,11 +50,23 @@ private static boolean isCompatible(ArgumentMatcher argumentMatcher, Object a return expectedArgumentType.isInstance(argument); } + private static Class getArgumentType(ArgumentMatcher matcher) { + Class argumentMatcherType = matcher.getClass(); + Class cached = argumentTypeCache.get(argumentMatcherType); + // Avoids a lambda allocation on invocations >=2 for worse perf on invocation 1. + if (cached != null) { + return cached; + } else { + return argumentTypeCache.computeIfAbsent( + argumentMatcherType, unusedKey -> getArgumentTypeUncached(matcher)); + } + } + /** * Returns the type of {@link ArgumentMatcher#matches(Object)} of the given * {@link ArgumentMatcher} implementation. */ - private static Class getArgumentType(ArgumentMatcher argumentMatcher) { + private static Class getArgumentTypeUncached(ArgumentMatcher argumentMatcher) { Method[] methods = argumentMatcher.getClass().getMethods(); for (Method method : methods) { diff --git a/src/main/java/org/mockito/internal/junit/DefaultStubbingLookupListener.java b/src/main/java/org/mockito/internal/junit/DefaultStubbingLookupListener.java index c71386346e..48ba775100 100644 --- a/src/main/java/org/mockito/internal/junit/DefaultStubbingLookupListener.java +++ b/src/main/java/org/mockito/internal/junit/DefaultStubbingLookupListener.java @@ -10,6 +10,7 @@ import java.util.Collection; import java.util.LinkedList; import java.util.List; +import java.util.Objects; import org.mockito.internal.exceptions.Reporter; import org.mockito.internal.stubbing.UnusedStubbingReporting; @@ -68,17 +69,15 @@ private static List potentialArgMismatches( List matchingStubbings = new LinkedList<>(); for (Stubbing s : stubbings) { if (UnusedStubbingReporting.shouldBeReported(s) - && s.getInvocation() - .getMethod() - .getName() - .equals(invocation.getMethod().getName()) + && Objects.equals( + s.getInvocation().getMethod().getName(), + invocation.getMethod().getName()) // If stubbing and invocation are in the same source file we assume they are in // the test code, // and we don't flag it as mismatch: - && !s.getInvocation() - .getLocation() - .getSourceFile() - .equals(invocation.getLocation().getSourceFile())) { + && !Objects.equals( + s.getInvocation().getLocation().getSourceFile(), + invocation.getLocation().getSourceFile())) { matchingStubbings.add(s.getInvocation()); } } diff --git a/src/main/java/org/mockito/internal/matchers/And.java b/src/main/java/org/mockito/internal/matchers/And.java index 417e88dbbe..cffed323f1 100644 --- a/src/main/java/org/mockito/internal/matchers/And.java +++ b/src/main/java/org/mockito/internal/matchers/And.java @@ -23,6 +23,13 @@ public boolean matches(Object actual) { return m1.matches(actual) && m2.matches(actual); } + @Override + public Class type() { + return m1.type().isAssignableFrom(m2.type()) + ? m1.type() + : m2.type().isAssignableFrom(m1.type()) ? m2.type() : ArgumentMatcher.super.type(); + } + @Override public String toString() { return "and(" + m1 + ", " + m2 + ")"; diff --git a/src/main/java/org/mockito/internal/matchers/Any.java b/src/main/java/org/mockito/internal/matchers/Any.java index 7ad113feed..2074f81abc 100644 --- a/src/main/java/org/mockito/internal/matchers/Any.java +++ b/src/main/java/org/mockito/internal/matchers/Any.java @@ -8,7 +8,7 @@ import org.mockito.ArgumentMatcher; -public class Any implements ArgumentMatcher, VarargMatcher, Serializable { +public class Any implements ArgumentMatcher, Serializable { public static final Any ANY = new Any(); @@ -21,4 +21,9 @@ public boolean matches(Object actual) { public String toString() { return ""; } + + @Override + public Class type() { + return Object.class; + } } diff --git a/src/main/java/org/mockito/internal/matchers/CapturingMatcher.java b/src/main/java/org/mockito/internal/matchers/CapturingMatcher.java index 5138839ddb..7ce9133651 100644 --- a/src/main/java/org/mockito/internal/matchers/CapturingMatcher.java +++ b/src/main/java/org/mockito/internal/matchers/CapturingMatcher.java @@ -9,30 +9,43 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.mockito.ArgumentMatcher; +import org.mockito.internal.util.Primitives; -@SuppressWarnings("unchecked") -public class CapturingMatcher - implements ArgumentMatcher, CapturesArguments, VarargMatcher, Serializable { +public class CapturingMatcher implements ArgumentMatcher, CapturesArguments, Serializable { - private final List arguments = new ArrayList<>(); + private final Class clazz; + private final List arguments = new ArrayList<>(); private final ReadWriteLock lock = new ReentrantReadWriteLock(); private final Lock readLock = lock.readLock(); private final Lock writeLock = lock.writeLock(); + public CapturingMatcher(final Class clazz) { + this.clazz = Objects.requireNonNull(clazz); + } + @Override public boolean matches(Object argument) { - return true; + if (argument == null) { + return true; + } + + if (Primitives.isPrimitiveOrWrapper(clazz)) { + return Primitives.isAssignableFromWrapper(clazz, argument.getClass()); + } + + return clazz.isAssignableFrom(argument.getClass()); } @Override public String toString() { - return ""; + return ""; } public T getLastValue() { @@ -42,7 +55,7 @@ public T getLastValue() { throw noArgumentValueWasCaptured(); } - return (T) arguments.get(arguments.size() - 1); + return arguments.get(arguments.size() - 1); } finally { readLock.unlock(); } @@ -51,19 +64,25 @@ public T getLastValue() { public List getAllValues() { readLock.lock(); try { - return new ArrayList((List) arguments); + return new ArrayList<>(arguments); } finally { readLock.unlock(); } } + @SuppressWarnings("unchecked") @Override public void captureFrom(Object argument) { writeLock.lock(); try { - this.arguments.add(argument); + this.arguments.add((T) argument); } finally { writeLock.unlock(); } } + + @Override + public Class type() { + return clazz; + } } diff --git a/src/main/java/org/mockito/internal/matchers/Equals.java b/src/main/java/org/mockito/internal/matchers/Equals.java index 8b07b2da75..bbfad627c4 100644 --- a/src/main/java/org/mockito/internal/matchers/Equals.java +++ b/src/main/java/org/mockito/internal/matchers/Equals.java @@ -22,6 +22,11 @@ public boolean matches(Object actual) { return Equality.areEqual(this.wanted, actual); } + @Override + public Class type() { + return wanted != null ? wanted.getClass() : ArgumentMatcher.super.type(); + } + @Override public String toString() { return describe(wanted); diff --git a/src/main/java/org/mockito/internal/matchers/InstanceOf.java b/src/main/java/org/mockito/internal/matchers/InstanceOf.java index 1b4b30e875..a53b1bda0f 100644 --- a/src/main/java/org/mockito/internal/matchers/InstanceOf.java +++ b/src/main/java/org/mockito/internal/matchers/InstanceOf.java @@ -11,7 +11,7 @@ public class InstanceOf implements ArgumentMatcher, Serializable { - private final Class clazz; + final Class clazz; private final String description; public InstanceOf(Class clazz) { @@ -31,18 +31,12 @@ public boolean matches(Object actual) { } @Override - public String toString() { - return description; + public Class type() { + return clazz; } - public static class VarArgAware extends InstanceOf implements VarargMatcher { - - public VarArgAware(Class clazz) { - super(clazz); - } - - public VarArgAware(Class clazz, String describedAs) { - super(clazz, describedAs); - } + @Override + public String toString() { + return description; } } diff --git a/src/main/java/org/mockito/internal/matchers/LocalizedMatcher.java b/src/main/java/org/mockito/internal/matchers/LocalizedMatcher.java index 00b47de7a9..a54eb6d79e 100644 --- a/src/main/java/org/mockito/internal/matchers/LocalizedMatcher.java +++ b/src/main/java/org/mockito/internal/matchers/LocalizedMatcher.java @@ -5,7 +5,7 @@ package org.mockito.internal.matchers; import org.mockito.ArgumentMatcher; -import org.mockito.internal.debugging.LocationImpl; +import org.mockito.internal.debugging.LocationFactory; import org.mockito.invocation.Location; @SuppressWarnings("unchecked") @@ -16,7 +16,7 @@ public class LocalizedMatcher { public LocalizedMatcher(ArgumentMatcher matcher) { this.matcher = matcher; - this.location = new LocationImpl(); + this.location = LocationFactory.create(); } public Location getLocation() { diff --git a/src/main/java/org/mockito/internal/matchers/Not.java b/src/main/java/org/mockito/internal/matchers/Not.java index bd35eafbad..62b91b5c64 100644 --- a/src/main/java/org/mockito/internal/matchers/Not.java +++ b/src/main/java/org/mockito/internal/matchers/Not.java @@ -22,6 +22,11 @@ public boolean matches(Object actual) { return !matcher.matches(actual); } + @Override + public Class type() { + return matcher.type(); + } + @Override public String toString() { return "not(" + matcher + ")"; diff --git a/src/main/java/org/mockito/internal/matchers/NotNull.java b/src/main/java/org/mockito/internal/matchers/NotNull.java index 2902f57061..f3f7fe7d05 100644 --- a/src/main/java/org/mockito/internal/matchers/NotNull.java +++ b/src/main/java/org/mockito/internal/matchers/NotNull.java @@ -5,20 +5,30 @@ package org.mockito.internal.matchers; import java.io.Serializable; +import java.util.Objects; import org.mockito.ArgumentMatcher; -public class NotNull implements ArgumentMatcher, Serializable { +public class NotNull implements ArgumentMatcher, Serializable { - public static final NotNull NOT_NULL = new NotNull(); + public static final NotNull NOT_NULL = new NotNull<>(Object.class); - private NotNull() {} + private final Class type; + + public NotNull(Class type) { + this.type = Objects.requireNonNull(type); + } @Override public boolean matches(Object actual) { return actual != null; } + @Override + public Class type() { + return type; + } + @Override public String toString() { return "notNull()"; diff --git a/src/main/java/org/mockito/internal/matchers/Null.java b/src/main/java/org/mockito/internal/matchers/Null.java index 69eee484a1..400170a5cc 100644 --- a/src/main/java/org/mockito/internal/matchers/Null.java +++ b/src/main/java/org/mockito/internal/matchers/Null.java @@ -5,20 +5,29 @@ package org.mockito.internal.matchers; import java.io.Serializable; +import java.util.Objects; import org.mockito.ArgumentMatcher; -public class Null implements ArgumentMatcher, Serializable { +public class Null implements ArgumentMatcher, Serializable { - public static final Null NULL = new Null(); + public static final Null NULL = new Null<>(Object.class); + private final Class type; - private Null() {} + public Null(Class type) { + this.type = Objects.requireNonNull(type); + } @Override public boolean matches(Object actual) { return actual == null; } + @Override + public Class type() { + return type; + } + @Override public String toString() { return "isNull()"; diff --git a/src/main/java/org/mockito/internal/matchers/Or.java b/src/main/java/org/mockito/internal/matchers/Or.java index ed7bbdeb4d..7354814323 100644 --- a/src/main/java/org/mockito/internal/matchers/Or.java +++ b/src/main/java/org/mockito/internal/matchers/Or.java @@ -23,6 +23,13 @@ public boolean matches(Object actual) { return m1.matches(actual) || m2.matches(actual); } + @Override + public Class type() { + return m1.type().isAssignableFrom(m2.type()) + ? m1.type() + : m2.type().isAssignableFrom(m1.type()) ? m2.type() : ArgumentMatcher.super.type(); + } + @Override public String toString() { return "or(" + m1 + ", " + m2 + ")"; diff --git a/src/main/java/org/mockito/internal/matchers/Same.java b/src/main/java/org/mockito/internal/matchers/Same.java index 0e23c5cd46..fa116b53f0 100644 --- a/src/main/java/org/mockito/internal/matchers/Same.java +++ b/src/main/java/org/mockito/internal/matchers/Same.java @@ -22,6 +22,11 @@ public boolean matches(Object actual) { return wanted == actual; } + @Override + public Class type() { + return wanted != null ? wanted.getClass() : ArgumentMatcher.super.type(); + } + @Override public String toString() { return "same(" + ValuePrinter.print(wanted) + ")"; diff --git a/src/main/java/org/mockito/internal/matchers/VarargMatcher.java b/src/main/java/org/mockito/internal/matchers/VarargMatcher.java deleted file mode 100644 index 43a27596f8..0000000000 --- a/src/main/java/org/mockito/internal/matchers/VarargMatcher.java +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright (c) 2007 Mockito contributors - * This program is made available under the terms of the MIT License. - */ -package org.mockito.internal.matchers; - -import java.io.Serializable; - -/** - * Internal interface that informs Mockito that the matcher is intended to capture varargs. - * This information is needed when mockito collects the arguments. - */ -public interface VarargMatcher extends Serializable {} diff --git a/src/main/java/org/mockito/internal/progress/MockingProgressImpl.java b/src/main/java/org/mockito/internal/progress/MockingProgressImpl.java index 991b5e4734..2585d32cfc 100644 --- a/src/main/java/org/mockito/internal/progress/MockingProgressImpl.java +++ b/src/main/java/org/mockito/internal/progress/MockingProgressImpl.java @@ -14,7 +14,7 @@ import org.mockito.internal.configuration.GlobalConfiguration; import org.mockito.internal.debugging.Localized; -import org.mockito.internal.debugging.LocationImpl; +import org.mockito.internal.debugging.LocationFactory; import org.mockito.internal.exceptions.Reporter; import org.mockito.internal.listeners.AutoCleanableListener; import org.mockito.invocation.Location; @@ -105,7 +105,7 @@ public VerificationMode pullVerificationMode() { @Override public void stubbingStarted() { validateState(); - stubbingInProgress = new LocationImpl(); + stubbingInProgress = LocationFactory.create(); } @Override diff --git a/src/main/java/org/mockito/internal/stubbing/InvocationContainerImpl.java b/src/main/java/org/mockito/internal/stubbing/InvocationContainerImpl.java index 5aedbb912e..832b1b155b 100644 --- a/src/main/java/org/mockito/internal/stubbing/InvocationContainerImpl.java +++ b/src/main/java/org/mockito/internal/stubbing/InvocationContainerImpl.java @@ -25,7 +25,6 @@ import org.mockito.stubbing.Stubbing; import org.mockito.stubbing.ValidableAnswer; -@SuppressWarnings("unchecked") public class InvocationContainerImpl implements InvocationContainer, Serializable { private static final long serialVersionUID = -5334301962749537177L; @@ -36,7 +35,7 @@ public class InvocationContainerImpl implements InvocationContainer, Serializabl private MatchableInvocation invocationForStubbing; - public InvocationContainerImpl(MockCreationSettings mockSettings) { + public InvocationContainerImpl(MockCreationSettings mockSettings) { this.registeredInvocations = createRegisteredInvocations(mockSettings); this.mockStrictness = mockSettings.getStrictness(); this.doAnswerStyleStubbing = new DoAnswerStyleStubbing(); @@ -51,14 +50,14 @@ public void resetInvocationForPotentialStubbing(MatchableInvocation invocationMa this.invocationForStubbing = invocationMatcher; } - public void addAnswer(Answer answer, Strictness stubbingStrictness) { + public void addAnswer(Answer answer, Strictness stubbingStrictness) { registeredInvocations.removeLast(); addAnswer(answer, false, stubbingStrictness); } /** Adds new stubbed answer and returns the invocation matcher the answer was added to. */ public StubbedInvocationMatcher addAnswer( - Answer answer, boolean isConsecutive, Strictness stubbingStrictness) { + Answer answer, boolean isConsecutive, Strictness stubbingStrictness) { Invocation invocation = invocationForStubbing.getInvocation(); mockingProgress().stubbingCompleted(); if (answer instanceof ValidableAnswer) { @@ -79,7 +78,7 @@ public StubbedInvocationMatcher addAnswer( } } - public void addConsecutiveAnswer(Answer answer) { + public void addConsecutiveAnswer(Answer answer) { addAnswer(answer, true, null); } @@ -143,18 +142,14 @@ public void clearInvocations() { registeredInvocations.clear(); } - /** - * Stubbings in descending order, most recent first - */ - public List getStubbingsDescending() { - return (List) stubbed; - } - /** * Stubbings in ascending order, most recent last */ public Collection getStubbingsAscending() { - List result = new LinkedList<>(stubbed); + List result; + synchronized (stubbed) { + result = new LinkedList<>(stubbed); + } Collections.reverse(result); return result; } @@ -163,13 +158,14 @@ public Object invokedMock() { return invocationForStubbing.getInvocation().getMock(); } - private RegisteredInvocations createRegisteredInvocations(MockCreationSettings mockSettings) { + private RegisteredInvocations createRegisteredInvocations( + MockCreationSettings mockSettings) { return mockSettings.isStubOnly() ? new SingleRegisteredInvocation() : new DefaultRegisteredInvocations(); } - public Answer findStubbedAnswer() { + public Answer findStubbedAnswer() { synchronized (stubbed) { for (StubbedInvocationMatcher s : stubbed) { if (invocationForStubbing.matches(s.getInvocation())) { diff --git a/src/main/java/org/mockito/internal/stubbing/answers/CallsRealMethods.java b/src/main/java/org/mockito/internal/stubbing/answers/CallsRealMethods.java index bab007bbb0..ae16991af8 100644 --- a/src/main/java/org/mockito/internal/stubbing/answers/CallsRealMethods.java +++ b/src/main/java/org/mockito/internal/stubbing/answers/CallsRealMethods.java @@ -17,10 +17,10 @@ /** * Optional Answer that adds partial mocking support *

      - * {@link Answer} can be used to define the return values of unstubbed invocations. + * {@link Answer} can be used to define the return values of un-stubbed invocations. *

      * This implementation can be helpful when working with legacy code. - * When this implementation is used, unstubbed methods will delegate to the real implementation. + * When this implementation is used, un-stubbed methods will delegate to the real implementation. * This is a way to create a partial mock object that calls real methods by default. *

      * As usual you are going to read the partial mock warning: diff --git a/src/main/java/org/mockito/internal/stubbing/defaultanswers/RetrieveGenericsForDefaultAnswers.java b/src/main/java/org/mockito/internal/stubbing/defaultanswers/RetrieveGenericsForDefaultAnswers.java index fa6a72f53d..8b64a16916 100644 --- a/src/main/java/org/mockito/internal/stubbing/defaultanswers/RetrieveGenericsForDefaultAnswers.java +++ b/src/main/java/org/mockito/internal/stubbing/defaultanswers/RetrieveGenericsForDefaultAnswers.java @@ -8,7 +8,6 @@ import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; -import org.mockito.internal.MockitoCore; import org.mockito.internal.util.MockUtil; import org.mockito.internal.util.reflection.GenericMetadataSupport; import org.mockito.invocation.InvocationOnMock; @@ -16,8 +15,6 @@ final class RetrieveGenericsForDefaultAnswers { - private static final MockitoCore MOCKITO_CORE = new MockitoCore(); - static Object returnTypeForMockWithCorrectGenerics( InvocationOnMock invocation, AnswerCallback answerCallback) { Class type = invocation.getMethod().getReturnType(); @@ -38,7 +35,9 @@ static Object returnTypeForMockWithCorrectGenerics( } if (type != null) { - if (!MOCKITO_CORE.isTypeMockable(type)) { + final MockCreationSettings mockSettings = + MockUtil.getMockSettings(invocation.getMock()); + if (!MockUtil.typeMockabilityOf(type, mockSettings.getMockMaker()).mockable()) { return null; } diff --git a/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsDeepStubs.java b/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsDeepStubs.java index 097b11b6e2..8356c1b79b 100644 --- a/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsDeepStubs.java +++ b/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsDeepStubs.java @@ -5,6 +5,7 @@ package org.mockito.internal.stubbing.defaultanswers; import static org.mockito.Mockito.withSettings; +import static org.mockito.internal.util.MockUtil.typeMockabilityOf; import java.io.IOException; import java.io.Serializable; @@ -50,9 +51,10 @@ public Object answer(InvocationOnMock invocation) throws Throwable { GenericMetadataSupport returnTypeGenericMetadata = actualParameterizedType(invocation.getMock()) .resolveGenericReturnType(invocation.getMethod()); + MockCreationSettings mockSettings = MockUtil.getMockSettings(invocation.getMock()); Class rawType = returnTypeGenericMetadata.rawType(); - if (!mockitoCore().isTypeMockable(rawType)) { + if (!typeMockabilityOf(rawType, mockSettings.getMockMaker()).mockable()) { if (invocation.getMethod().getReturnType().equals(rawType)) { return delegate().answer(invocation); } else { @@ -119,7 +121,7 @@ private Object newDeepStubMock( private MockSettings withSettingsUsing( GenericMetadataSupport returnTypeGenericMetadata, - MockCreationSettings parentMockSettings) { + MockCreationSettings parentMockSettings) { MockSettings mockSettings = returnTypeGenericMetadata.hasRawExtraInterfaces() ? withSettings() @@ -127,7 +129,8 @@ private MockSettings withSettingsUsing( : withSettings(); return propagateSerializationSettings(mockSettings, parentMockSettings) - .defaultAnswer(returnsDeepStubsAnswerUsing(returnTypeGenericMetadata)); + .defaultAnswer(returnsDeepStubsAnswerUsing(returnTypeGenericMetadata)) + .mockMaker(parentMockSettings.getMockMaker()); } private MockSettings propagateSerializationSettings( diff --git a/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsEmptyValues.java b/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsEmptyValues.java index ad8e7e2466..20e9d85afe 100644 --- a/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsEmptyValues.java +++ b/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsEmptyValues.java @@ -8,6 +8,8 @@ import static org.mockito.internal.util.ObjectMethodsGuru.isToStringMethod; import java.io.Serializable; +import java.time.Duration; +import java.time.Period; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -17,12 +19,20 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; import java.util.Set; import java.util.SortedMap; import java.util.SortedSet; import java.util.TreeMap; import java.util.TreeSet; -import org.mockito.internal.util.JavaEightUtil; +import java.util.stream.DoubleStream; +import java.util.stream.IntStream; +import java.util.stream.LongStream; +import java.util.stream.Stream; + import org.mockito.internal.util.MockUtil; import org.mockito.internal.util.Primitives; import org.mockito.invocation.InvocationOnMock; @@ -129,26 +139,26 @@ Object returnValueFor(Class type) { return new TreeMap<>(); } else if (type == LinkedHashMap.class) { return new LinkedHashMap<>(); - } else if ("java.util.Optional".equals(type.getName())) { - return JavaEightUtil.emptyOptional(); - } else if ("java.util.OptionalDouble".equals(type.getName())) { - return JavaEightUtil.emptyOptionalDouble(); - } else if ("java.util.OptionalInt".equals(type.getName())) { - return JavaEightUtil.emptyOptionalInt(); - } else if ("java.util.OptionalLong".equals(type.getName())) { - return JavaEightUtil.emptyOptionalLong(); - } else if ("java.util.stream.Stream".equals(type.getName())) { - return JavaEightUtil.emptyStream(); - } else if ("java.util.stream.DoubleStream".equals(type.getName())) { - return JavaEightUtil.emptyDoubleStream(); - } else if ("java.util.stream.IntStream".equals(type.getName())) { - return JavaEightUtil.emptyIntStream(); - } else if ("java.util.stream.LongStream".equals(type.getName())) { - return JavaEightUtil.emptyLongStream(); - } else if ("java.time.Duration".equals(type.getName())) { - return JavaEightUtil.emptyDuration(); - } else if ("java.time.Period".equals(type.getName())) { - return JavaEightUtil.emptyPeriod(); + } else if (type == Optional.class) { + return Optional.empty(); + } else if (type == OptionalDouble.class) { + return OptionalDouble.empty(); + } else if (type == OptionalInt.class) { + return OptionalInt.empty(); + } else if (type == OptionalLong.class) { + return OptionalLong.empty(); + } else if (type == Stream.class) { + return Stream.empty(); + } else if (type == DoubleStream.class) { + return DoubleStream.empty(); + } else if (type == IntStream.class) { + return IntStream.empty(); + } else if (type == LongStream.class) { + return LongStream.empty(); + } else if (type == Duration.class) { + return Duration.ZERO; + } else if (type == Period.class) { + return Period.ZERO; } // Let's not care about the rest of collections. diff --git a/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsMocks.java b/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsMocks.java index 0ef6f0d954..c155780916 100755 --- a/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsMocks.java +++ b/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsMocks.java @@ -8,7 +8,9 @@ import org.mockito.Mockito; import org.mockito.internal.creation.MockSettingsImpl; +import org.mockito.internal.util.MockUtil; import org.mockito.invocation.InvocationOnMock; +import org.mockito.mock.MockCreationSettings; import org.mockito.stubbing.Answer; public class ReturnsMocks implements Answer, Serializable { @@ -33,9 +35,14 @@ public Object apply(Class type) { return null; } + MockCreationSettings mockSettings = + MockUtil.getMockSettings(invocation.getMock()); + return Mockito.mock( type, - new MockSettingsImpl().defaultAnswer(ReturnsMocks.this)); + new MockSettingsImpl<>() + .defaultAnswer(ReturnsMocks.this) + .mockMaker(mockSettings.getMockMaker())); } }); } diff --git a/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsSmartNulls.java b/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsSmartNulls.java index 2402f364a6..d538784a94 100644 --- a/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsSmartNulls.java +++ b/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsSmartNulls.java @@ -8,23 +8,29 @@ import static org.mockito.internal.util.ObjectMethodsGuru.isToStringMethod; import java.io.Serializable; +import java.lang.reflect.Method; +import java.util.Arrays; import org.mockito.Mockito; -import org.mockito.internal.debugging.LocationImpl; +import org.mockito.internal.creation.MockSettingsImpl; +import org.mockito.internal.creation.bytebuddy.MockAccess; +import org.mockito.internal.debugging.LocationFactory; +import org.mockito.internal.util.MockUtil; import org.mockito.invocation.InvocationOnMock; import org.mockito.invocation.Location; +import org.mockito.mock.MockCreationSettings; import org.mockito.stubbing.Answer; /** * Optional Answer that can be used with * {@link Mockito#mock(Class, Answer)} *

      - * This implementation can be helpful when working with legacy code. Unstubbed + * This implementation can be helpful when working with legacy code. Un-stubbed * methods often return null. If your code uses the object returned by an - * unstubbed call you get a NullPointerException. This implementation of + * un-stubbed call, you get a NullPointerException. This implementation of * Answer returns SmartNulls instead of nulls. * SmartNull gives nicer exception message than NPE because it points out the - * line where unstubbed method was called. You just click on the stack trace. + * line where un-stubbed method was called. You just click on the stack trace. *

      * ReturnsSmartNulls first tries to return ordinary return values (see * {@link ReturnsMoreEmptyValues}) then it tries to return SmartNull. If the @@ -56,8 +62,16 @@ public Object apply(Class type) { return null; } + MockCreationSettings mockSettings = + MockUtil.getMockSettings(invocation.getMock()); + Answer defaultAnswer = + new ThrowsSmartNullPointer(invocation, LocationFactory.create()); + return Mockito.mock( - type, new ThrowsSmartNullPointer(invocation, new LocationImpl())); + type, + new MockSettingsImpl<>() + .defaultAnswer(defaultAnswer) + .mockMaker(mockSettings.getMockMaker())); } }); } @@ -76,11 +90,30 @@ private static class ThrowsSmartNullPointer implements Answer { @Override public Object answer(InvocationOnMock currentInvocation) throws Throwable { if (isToStringMethod(currentInvocation.getMethod())) { - return "SmartNull returned by this unstubbed method call on a mock:\n" + return "SmartNull returned by this un-stubbed method call on a mock:\n" + unstubbedInvocation; + } else if (isMethodOf( + MockAccess.class, currentInvocation.getMock(), currentInvocation.getMethod())) { + /* The MockAccess methods should be called directly */ + return currentInvocation.callRealMethod(); } throw smartNullPointerException(unstubbedInvocation.toString(), location); } + + private static boolean isMethodOf(Class clazz, Object instance, Method method) { + if (!clazz.isInstance(instance)) { + return false; + } + + for (Method m : clazz.getDeclaredMethods()) { + if (m.getName().equalsIgnoreCase(method.getName()) + && Arrays.equals(m.getParameterTypes(), method.getParameterTypes())) { + return true; + } + } + + return false; + } } } diff --git a/src/main/java/org/mockito/internal/util/JavaEightUtil.java b/src/main/java/org/mockito/internal/util/JavaEightUtil.java deleted file mode 100644 index 20c6e80c3b..0000000000 --- a/src/main/java/org/mockito/internal/util/JavaEightUtil.java +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Copyright (c) 2016 Mockito contributors - * This program is made available under the terms of the MIT License. - */ -package org.mockito.internal.util; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; - -import org.mockito.creation.instance.InstantiationException; - -/** - * Helper class to work with features that were introduced in Java versions after 1.5. - * This class uses reflection in most places to avoid coupling with a newer JDK. - */ -public final class JavaEightUtil { - - // No need for volatile, these optionals are already safe singletons. - private static Object emptyOptional; - private static Object emptyOptionalDouble; - private static Object emptyOptionalInt; - private static Object emptyOptionalLong; - private static Object emptyDuration; - private static Object emptyPeriod; - - private JavaEightUtil() { - // utility class - } - - /** - * Creates an empty Optional using reflection to stay backwards-compatible with older JDKs. - * - * @return an empty Optional. - */ - public static Object emptyOptional() { - // no need for double-checked locking - if (emptyOptional != null) { - return emptyOptional; - } - - return emptyOptional = invokeNullaryFactoryMethod("java.util.Optional", "empty"); - } - - /** - * Creates an empty OptionalDouble using reflection to stay backwards-compatible with older JDKs. - * - * @return an empty OptionalDouble. - */ - public static Object emptyOptionalDouble() { - // no need for double-checked locking - if (emptyOptionalDouble != null) { - return emptyOptionalDouble; - } - - return emptyOptionalDouble = - invokeNullaryFactoryMethod("java.util.OptionalDouble", "empty"); - } - - /** - * Creates an empty OptionalInt using reflection to stay backwards-compatible with older JDKs. - * - * @return an empty OptionalInt. - */ - public static Object emptyOptionalInt() { - // no need for double-checked locking - if (emptyOptionalInt != null) { - return emptyOptionalInt; - } - - return emptyOptionalInt = invokeNullaryFactoryMethod("java.util.OptionalInt", "empty"); - } - - /** - * Creates an empty OptionalLong using reflection to stay backwards-compatible with older JDKs. - * - * @return an empty OptionalLong. - */ - public static Object emptyOptionalLong() { - // no need for double-checked locking - if (emptyOptionalLong != null) { - return emptyOptionalLong; - } - - return emptyOptionalLong = invokeNullaryFactoryMethod("java.util.OptionalLong", "empty"); - } - - /** - * Creates an empty Stream using reflection to stay backwards-compatible with older JDKs. - * - * @return an empty Stream. - */ - public static Object emptyStream() { - // note: the empty stream can not be stored as a singleton. - return invokeNullaryFactoryMethod("java.util.stream.Stream", "empty"); - } - - /** - * Creates an empty DoubleStream using reflection to stay backwards-compatible with older JDKs. - * - * @return an empty DoubleStream. - */ - public static Object emptyDoubleStream() { - // note: the empty stream can not be stored as a singleton. - return invokeNullaryFactoryMethod("java.util.stream.DoubleStream", "empty"); - } - - /** - * Creates an empty IntStream using reflection to stay backwards-compatible with older JDKs. - * - * @return an empty IntStream. - */ - public static Object emptyIntStream() { - // note: the empty stream can not be stored as a singleton. - return invokeNullaryFactoryMethod("java.util.stream.IntStream", "empty"); - } - - /** - * Creates an empty LongStream using reflection to stay backwards-compatible with older JDKs. - * - * @return an empty LongStream. - */ - public static Object emptyLongStream() { - // note: the empty stream can not be stored as a singleton. - return invokeNullaryFactoryMethod("java.util.stream.LongStream", "empty"); - } - - /** - * Creates an empty Duration using reflection to stay backwards-compatible with older JDKs. - * - * @return an empty (ZERO) Duration. - */ - public static Object emptyDuration() { - // no need for double-checked locking - if (emptyDuration != null) { - return emptyDuration; - } - - return emptyDuration = getStaticFieldValue("java.time.Duration", "ZERO"); - } - - /** - * Creates an empty Period using reflection to stay backwards-compatible with older JDKs. - * - * @return an empty (ZERO) Period. - */ - public static Object emptyPeriod() { - // no need for double-checked locking - if (emptyPeriod != null) { - return emptyPeriod; - } - - return emptyPeriod = getStaticFieldValue("java.time.Period", "ZERO"); - } - - /** - * Invokes a nullary static factory method using reflection to stay backwards-compatible with older JDKs. - * - * @param fqcn The fully qualified class name of the type to be produced. - * @param methodName The name of the factory method. - * @return the object produced. - */ - private static Object invokeNullaryFactoryMethod(final String fqcn, final String methodName) { - try { - final Method method = getMethod(fqcn, methodName); - return method.invoke(null); - // any exception is really unexpected since the type name has - // already been verified - } catch (final Exception e) { - throw new InstantiationException( - String.format("Could not create %s#%s(): %s", fqcn, methodName, e), e); - } - } - - /** - * Gets a value of the classes' field using reflection to stay backwards-compatible with older JDKs. - * - * @param fqcn The fully qualified class name of the type to be produced. - * @param fieldName The name of th classes' field which value is going to be returned. - * @return the restored value. - */ - private static Object getStaticFieldValue(final String fqcn, final String fieldName) { - try { - final Class type = getClass(fqcn); - final Field field = type.getField(fieldName); - return field.get(null); - // any exception is really unexpected since the type name has - // already been verified - } catch (Exception e) { - throw new InstantiationException( - String.format("Could not get %s#%s(): %s", fqcn, fieldName, e), e); - } - } - - /** - * Returns the {@code Class} object associated with the class or interface with the given string name. - * - * @param fqcn The fully qualified class name of the type to be produced. - * @return the Class object for the class with the specified name. - */ - private static Class getClass(String fqcn) { - try { - return Class.forName(fqcn); - // any exception is really unexpected since the type name has - // already been verified - } catch (ClassNotFoundException e) { - throw new InstantiationException(String.format("Could not find %s: %s", fqcn, e), e); - } - } - - /** - * Returns a Method object that reflects the specified public member method of the class or interface represented by the fully qualified class name. - * - * @param fqcn The fully qualified class name of the type to be produced. - * @param methodName The name of the method. - * @param parameterClasses The list of parameters. - * @return The Method object that matches the specified name and parameterTypes. - */ - private static Method getMethod( - final String fqcn, final String methodName, final Class... parameterClasses) { - try { - final Class type = getClass(fqcn); - return type.getMethod(methodName, parameterClasses); - } catch (Exception e) { - throw new InstantiationException( - String.format("Could not find %s#%s(): %s", fqcn, methodName, e), e); - } - } -} diff --git a/src/main/java/org/mockito/internal/util/MockCreationValidator.java b/src/main/java/org/mockito/internal/util/MockCreationValidator.java index 7baa4e252f..9db6b36ea3 100644 --- a/src/main/java/org/mockito/internal/util/MockCreationValidator.java +++ b/src/main/java/org/mockito/internal/util/MockCreationValidator.java @@ -18,8 +18,8 @@ @SuppressWarnings("unchecked") public class MockCreationValidator { - public void validateType(Class classToMock) { - TypeMockability typeMockability = MockUtil.typeMockabilityOf(classToMock); + public void validateType(Class classToMock, String mockMaker) { + TypeMockability typeMockability = MockUtil.typeMockabilityOf(classToMock, mockMaker); if (!typeMockability.mockable()) { throw cannotMockClass(classToMock, typeMockability.nonMockableReason()); } diff --git a/src/main/java/org/mockito/internal/util/MockUtil.java b/src/main/java/org/mockito/internal/util/MockUtil.java index 2159608623..0d80f6e195 100644 --- a/src/main/java/org/mockito/internal/util/MockUtil.java +++ b/src/main/java/org/mockito/internal/util/MockUtil.java @@ -7,6 +7,7 @@ import org.mockito.MockedConstruction; import org.mockito.Mockito; import org.mockito.exceptions.misusing.NotAMockException; +import org.mockito.internal.configuration.plugins.DefaultMockitoPlugins; import org.mockito.internal.configuration.plugins.Plugins; import org.mockito.internal.creation.settings.CreationSettings; import org.mockito.internal.stubbing.InvocationContainerImpl; @@ -18,6 +19,9 @@ import org.mockito.plugins.MockMaker.TypeMockability; import org.mockito.plugins.MockResolver; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import static org.mockito.internal.handler.MockHandlerFactory.createMockHandler; @@ -25,15 +29,57 @@ @SuppressWarnings("unchecked") public class MockUtil { - private static final MockMaker mockMaker = Plugins.getMockMaker(); + private static final MockMaker defaultMockMaker = Plugins.getMockMaker(); + private static final Map, MockMaker> mockMakers = + new ConcurrentHashMap<>( + Collections.singletonMap(defaultMockMaker.getClass(), defaultMockMaker)); private MockUtil() {} - public static TypeMockability typeMockabilityOf(Class type) { - return mockMaker.isTypeMockable(type); + private static MockMaker getMockMaker(String mockMaker) { + if (mockMaker == null) { + return defaultMockMaker; + } + + String typeName; + if (DefaultMockitoPlugins.MOCK_MAKER_ALIASES.contains(mockMaker)) { + typeName = DefaultMockitoPlugins.getDefaultPluginClass(mockMaker); + } else { + typeName = mockMaker; + } + + Class type; + // Using the context class loader because PluginInitializer.loadImpl is using it as well. + // Personally, I am suspicious whether the context class loader is a good choice in either + // of these cases. + ClassLoader loader = Thread.currentThread().getContextClassLoader(); + if (loader == null) { + loader = ClassLoader.getSystemClassLoader(); + } + try { + type = loader.loadClass(typeName).asSubclass(MockMaker.class); + } catch (Exception e) { + throw new IllegalStateException("Failed to load MockMaker: " + mockMaker, e); + } + + return mockMakers.computeIfAbsent( + type, + t -> { + try { + return t.getDeclaredConstructor().newInstance(); + } catch (Exception e) { + throw new IllegalStateException( + "Failed to construct MockMaker: " + t.getName(), e); + } + }); + } + + public static TypeMockability typeMockabilityOf(Class type, String mockMaker) { + return getMockMaker(mockMaker).isTypeMockable(type); } public static T createMock(MockCreationSettings settings) { + MockMaker mockMaker = getMockMaker(settings.getMockMaker()); MockHandler mockHandler = createMockHandler(settings); Object spiedInstance = settings.getSpiedInstance(); @@ -62,17 +108,11 @@ public static void resetMock(Object mock) { MockHandler newHandler = createMockHandler(settings); mock = resolve(mock); - mockMaker.resetMock(mock, newHandler, settings); + getMockMaker(settings.getMockMaker()).resetMock(mock, newHandler, settings); } public static MockHandler getMockHandler(Object mock) { - if (mock == null) { - throw new NotAMockException("Argument should be a mock, but is null!"); - } - - mock = resolve(mock); - - MockHandler handler = mockMaker.getHandler(mock); + MockHandler handler = getMockHandlerOrNull(mock); if (handler != null) { return handler; } else { @@ -104,10 +144,24 @@ public static boolean isMock(Object mock) { if (mock == null) { return false; } + return getMockHandlerOrNull(mock) != null; + } + + private static MockHandler getMockHandlerOrNull(Object mock) { + if (mock == null) { + throw new NotAMockException("Argument should be a mock, but is null!"); + } mock = resolve(mock); - return mockMaker.getHandler(mock) != null; + for (MockMaker mockMaker : mockMakers.values()) { + MockHandler handler = mockMaker.getHandler(mock); + if (handler != null) { + assert getMockMaker(handler.getMockSettings().getMockMaker()) == mockMaker; + return handler; + } + } + return null; } private static Object resolve(Object mock) { @@ -143,6 +197,7 @@ public static MockCreationSettings getMockSettings(Object mock) { public static MockMaker.StaticMockControl createStaticMock( Class type, MockCreationSettings settings) { + MockMaker mockMaker = getMockMaker(settings.getMockMaker()); MockHandler handler = createMockHandler(settings); return mockMaker.createStaticMock(type, settings, handler); } @@ -153,11 +208,13 @@ public static MockMaker.ConstructionMockControl createConstructionMock( MockedConstruction.MockInitializer mockInitializer) { Function> handlerFactory = context -> createMockHandler(settingsFactory.apply(context)); - return mockMaker.createConstructionMock( + return defaultMockMaker.createConstructionMock( type, settingsFactory, handlerFactory, mockInitializer); } public static void clearAllCaches() { - mockMaker.clearAllCaches(); + for (MockMaker mockMaker : mockMakers.values()) { + mockMaker.clearAllCaches(); + } } } diff --git a/src/main/java/org/mockito/internal/util/Platform.java b/src/main/java/org/mockito/internal/util/Platform.java index fde59fbbe9..6ff0378280 100644 --- a/src/main/java/org/mockito/internal/util/Platform.java +++ b/src/main/java/org/mockito/internal/util/Platform.java @@ -7,15 +7,9 @@ import static org.mockito.internal.util.StringUtil.join; import java.util.Locale; -import java.util.regex.Matcher; -import java.util.regex.Pattern; public abstract class Platform { - private static final Pattern JAVA_8_RELEASE_VERSION_SCHEME = - Pattern.compile("1\\.8\\.0_(\\d+)(?:-ea)?(?:-b\\d+)?"); - private static final Pattern JAVA_8_DEV_VERSION_SCHEME = - Pattern.compile("1\\.8\\.0b\\d+_u(\\d+)"); public static final String JAVA_VERSION = System.getProperty("java.specification.version"); public static final String JVM_VERSION = System.getProperty("java.runtime.version"); public static final String JVM_VENDOR = System.getProperty("java.vm.vendor"); @@ -68,31 +62,6 @@ public static String describe() { return description; } - public static boolean isJava8BelowUpdate45() { - if (JVM_VERSION == null) { - return false; - } else { - return isJava8BelowUpdate45(JVM_VERSION); - } - } - - static boolean isJava8BelowUpdate45(String jvmVersion) { - Matcher matcher = JAVA_8_RELEASE_VERSION_SCHEME.matcher(jvmVersion); - if (matcher.matches()) { - int update = Integer.parseInt(matcher.group(1)); - return update < 45; - } - - matcher = JAVA_8_DEV_VERSION_SCHEME.matcher(jvmVersion); - if (matcher.matches()) { - int update = Integer.parseInt(matcher.group(1)); - return update < 45; - } - - matcher = Pattern.compile("1\\.8\\.0-b\\d+").matcher(jvmVersion); - return matcher.matches(); - } - public static String warnForVM( String vmName1, String warnMessage1, String vmName2, String warnMessage2) { return warnForVM(JVM_NAME, vmName1, warnMessage1, vmName2, warnMessage2); diff --git a/src/main/java/org/mockito/internal/util/io/IOUtil.java b/src/main/java/org/mockito/internal/util/io/IOUtil.java index 1223cbe93e..f77dfda1c3 100644 --- a/src/main/java/org/mockito/internal/util/io/IOUtil.java +++ b/src/main/java/org/mockito/internal/util/io/IOUtil.java @@ -7,11 +7,12 @@ import java.io.BufferedReader; import java.io.Closeable; import java.io.File; -import java.io.FileWriter; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.io.PrintWriter; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.LinkedList; import java.util.List; @@ -24,12 +25,12 @@ public final class IOUtil { /** - * Writes text to file + * Writes text to file in UTF-8. */ public static void writeText(String text, File output) { - PrintWriter pw = null; + OutputStreamWriter pw = null; try { - pw = new PrintWriter(new FileWriter(output)); + pw = new OutputStreamWriter(new FileOutputStream(output), StandardCharsets.UTF_8); pw.write(text); } catch (Exception e) { throw new MockitoException("Problems writing text to file: " + output, e); @@ -38,9 +39,12 @@ public static void writeText(String text, File output) { } } + /** + * Reads all lines from the given stream. Uses UTF-8. + */ public static Collection readLines(InputStream is) { List out = new LinkedList<>(); - BufferedReader r = new BufferedReader(new InputStreamReader(is)); + BufferedReader r = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8)); String line; try { while ((line = r.readLine()) != null) { diff --git a/src/main/java/org/mockito/internal/util/reflection/FieldInitializer.java b/src/main/java/org/mockito/internal/util/reflection/FieldInitializer.java index 3f104d1f02..7d51a6bb1b 100644 --- a/src/main/java/org/mockito/internal/util/reflection/FieldInitializer.java +++ b/src/main/java/org/mockito/internal/util/reflection/FieldInitializer.java @@ -259,7 +259,11 @@ public int compare(Constructor constructorA, Constructor constructorB) { private int countMockableParams(Constructor constructor) { int constructorMockableParamsSize = 0; for (Class aClass : constructor.getParameterTypes()) { - if (MockUtil.typeMockabilityOf(aClass).mockable()) { + // The argResolver already knows the concrete types it can provide. + // Instead of checking for mockability, I think it would be better to + // ask the argResolver whether it can resolve this type. + // Anyway, I keep it for now to avoid breaking any existing code. + if (MockUtil.typeMockabilityOf(aClass, null).mockable()) { constructorMockableParamsSize++; } } diff --git a/src/main/java/org/mockito/internal/util/reflection/InstrumentationMemberAccessor.java b/src/main/java/org/mockito/internal/util/reflection/InstrumentationMemberAccessor.java index 0a972d8019..61b3f06c46 100644 --- a/src/main/java/org/mockito/internal/util/reflection/InstrumentationMemberAccessor.java +++ b/src/main/java/org/mockito/internal/util/reflection/InstrumentationMemberAccessor.java @@ -188,7 +188,16 @@ public Object newInstance( onConstruction.invoke( () -> { try { - return DISPATCHER.invokeWithArguments(handle, arguments); + // Use handle.asFixedArity() to handle varargs bindings properly + // (as by default Java will create method handle with + // asVarargsCollector), fe: + // + // private static class Varargs { + // Varargs(String whatever, Observer... observers) { + // } + // } + return DISPATCHER.invokeWithArguments( + handle.asFixedArity(), arguments); } catch (Throwable throwable) { thrown.set(true); return throwable; @@ -235,6 +244,9 @@ public Object invoke(Method method, Object target, Object... arguments) if (!Modifier.isStatic(method.getModifiers())) { handle = handle.bindTo(target); } + if (handle.isVarargsCollector()) { + handle = handle.asFixedArity(); + } try { return DISPATCHER.invokeWithArguments(handle, arguments); } catch (Throwable t) { @@ -369,17 +381,23 @@ private static void assureArguments( throw new IllegalArgumentException("Cannot access " + target + " on " + owner); } } - if (types.length != values.length) { + + Object[] args = values; + if (args == null) { + args = new Object[0]; + } + + if (types.length != args.length) { throw new IllegalArgumentException( "Incorrect number of arguments for " + target + ": expected " + types.length + " but recevied " - + values.length); + + args.length); } - for (int index = 0; index < values.length; index++) { - if (values[index] == null) { + for (int index = 0; index < args.length; index++) { + if (args[index] == null) { if (types[index].isPrimitive()) { throw new IllegalArgumentException( "Cannot assign null to primitive type " @@ -391,10 +409,10 @@ private static void assureArguments( } } else { Class resolved = WRAPPERS.getOrDefault(types[index], types[index]); - if (!resolved.isAssignableFrom(values[index].getClass())) { + if (!resolved.isAssignableFrom(args[index].getClass())) { throw new IllegalArgumentException( "Cannot assign value of type " - + values[index].getClass() + + args[index].getClass() + " to " + resolved + " for " diff --git a/src/main/java/org/mockito/internal/verification/argumentmatching/ArgumentMatchingTool.java b/src/main/java/org/mockito/internal/verification/argumentmatching/ArgumentMatchingTool.java index 060ea0913e..1959147fc1 100644 --- a/src/main/java/org/mockito/internal/verification/argumentmatching/ArgumentMatchingTool.java +++ b/src/main/java/org/mockito/internal/verification/argumentmatching/ArgumentMatchingTool.java @@ -4,6 +4,8 @@ */ package org.mockito.internal.verification.argumentmatching; +import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; @@ -15,7 +17,7 @@ import org.mockito.ArgumentMatcher; import org.mockito.internal.matchers.ContainsExtraTypeInfo; -@SuppressWarnings("unchecked") +@SuppressWarnings("rawtypes") public class ArgumentMatchingTool { private ArgumentMatchingTool() {} @@ -42,6 +44,28 @@ && toStringEquals(m, arguments[i]) return suspicious.toArray(new Integer[0]); } + /** + * Returns indexes of arguments not matching the provided matchers. + */ + public static List getNotMatchingArgsIndexes( + List matchers, Object[] arguments) { + if (matchers.size() != arguments.length) { + return Collections.emptyList(); + } + + List nonMatching = new ArrayList<>(); + int i = 0; + for (ArgumentMatcher m : matchers) { + if (!safelyMatches(m, arguments[i])) { + nonMatching.add(i); + } + + i++; + } + + return nonMatching; + } + private static boolean safelyMatches(ArgumentMatcher m, Object arg) { try { return m.matches(arg); diff --git a/src/main/java/org/mockito/internal/verification/checkers/MissingInvocationChecker.java b/src/main/java/org/mockito/internal/verification/checkers/MissingInvocationChecker.java index 9ceb483008..03c3d8b462 100644 --- a/src/main/java/org/mockito/internal/verification/checkers/MissingInvocationChecker.java +++ b/src/main/java/org/mockito/internal/verification/checkers/MissingInvocationChecker.java @@ -52,7 +52,11 @@ public static void checkMissingInvocation( invocations.stream().map(Invocation::getLocation).collect(Collectors.toList()); throw argumentsAreDifferent( - smartPrinter.getWanted(), smartPrinter.getActuals(), actualLocations); + similar, + wanted, + smartPrinter.getWanted(), + smartPrinter.getActuals(), + actualLocations); } public static void checkMissingInvocation( diff --git a/src/main/java/org/mockito/junit/MockitoJUnitRunner.java b/src/main/java/org/mockito/junit/MockitoJUnitRunner.java index f7f4e1b928..bfc938e2dc 100644 --- a/src/main/java/org/mockito/junit/MockitoJUnitRunner.java +++ b/src/main/java/org/mockito/junit/MockitoJUnitRunner.java @@ -34,7 +34,7 @@ * See {@link UnnecessaryStubbingException}. * Similar to JUnit rules, the runner also reports stubbing argument mismatches as console warnings * (see {@link MockitoHint}). - * To opt-out from this feature, use {@code}@RunWith(MockitoJUnitRunner.Silent.class){@code} + * To opt-out from this feature, use {@code @RunWith(MockitoJUnitRunner.Silent.class)} *

    • * Initializes mocks annotated with {@link Mock}, * so that explicit usage of {@link MockitoAnnotations#openMocks(Object)} is not necessary. @@ -68,7 +68,7 @@ * } * * - * If you would like to take advantage of Mockito JUnit runner features + * If you would like to take advantage of Mockito JUnit runner features, * but you cannot use the runner there is a solution! * {@link MockitoSession} API is intended to offer cleaner tests and improved debuggability * to users that cannot use Mockito's built-in JUnit support (runner or the rule). @@ -154,7 +154,7 @@ public MockitoJUnitRunner(Class klass) throws InvocationTargetException { this(new StrictRunner(new RunnerFactory().createStrict(klass), klass)); } - MockitoJUnitRunner(InternalRunner runner) throws InvocationTargetException { + MockitoJUnitRunner(InternalRunner runner) { this.runner = runner; } diff --git a/src/main/java/org/mockito/mock/MockCreationSettings.java b/src/main/java/org/mockito/mock/MockCreationSettings.java index f7f0b96028..949af03b2e 100644 --- a/src/main/java/org/mockito/mock/MockCreationSettings.java +++ b/src/main/java/org/mockito/mock/MockCreationSettings.java @@ -4,6 +4,7 @@ */ package org.mockito.mock; +import java.lang.reflect.Type; import java.util.List; import java.util.Set; @@ -12,6 +13,7 @@ import org.mockito.listeners.InvocationListener; import org.mockito.listeners.StubbingLookupListener; import org.mockito.listeners.VerificationStartedListener; +import org.mockito.plugins.MockMaker; import org.mockito.quality.Strictness; import org.mockito.stubbing.Answer; @@ -26,6 +28,11 @@ public interface MockCreationSettings { */ Class getTypeToMock(); + /** + * The generic type of the mock, if any. + */ + Type getGenericTypeToMock(); + /** * the extra interfaces the mock object should implement. */ @@ -135,4 +142,13 @@ public interface MockCreationSettings { * @since 4.6.0 */ Strictness getStrictness(); + + /** + * Returns the {@link MockMaker} which shall be used to create the mock. + * When the return value is {@code null}, the default shall be used. + * + * @see MockSettings#mockMaker(String) + * @since 4.8.0 + */ + String getMockMaker(); } diff --git a/src/main/java/org/mockito/plugins/MockMaker.java b/src/main/java/org/mockito/plugins/MockMaker.java index 93a87ef0a5..c0b1cbcd2d 100644 --- a/src/main/java/org/mockito/plugins/MockMaker.java +++ b/src/main/java/org/mockito/plugins/MockMaker.java @@ -4,6 +4,7 @@ */ package org.mockito.plugins; +import org.mockito.MockSettings; import org.mockito.MockedConstruction; import org.mockito.exceptions.base.MockitoException; import org.mockito.invocation.MockHandler; @@ -45,6 +46,19 @@ *

      Note that if several mockito-extensions/org.mockito.plugins.MockMaker files exists in the classpath * Mockito will only use the first returned by the standard {@link ClassLoader#getResource} mechanism. * + *

      Using the MockSettings of individual mocks

      + * + *

      If you want to use a {@code MockMaker} only for a specific mock, + * you can specify it using {@link MockSettings#mockMaker(String)}.

      + *
      + *     // Use a built-in mock maker
      + *     Object mock = Mockito.mock(Object.class, Mockito.withSettings()
      + *             .mockMaker(MockMakers.INLINE));
      + *     // Or load a mock maker using a fully qualified class name
      + *     Object mock = Mockito.mock(Object.class, Mockito.withSettings()
      + *             .mockMaker("org.awesome.mockito.AwesomeMockMaker"));
      + * 
      + * * @see org.mockito.mock.MockCreationSettings * @see org.mockito.invocation.MockHandler * @since 1.9.5 diff --git a/src/main/java/org/mockito/verification/VerificationWithTimeout.java b/src/main/java/org/mockito/verification/VerificationWithTimeout.java index ad110d0cce..3e70d116bb 100644 --- a/src/main/java/org/mockito/verification/VerificationWithTimeout.java +++ b/src/main/java/org/mockito/verification/VerificationWithTimeout.java @@ -12,8 +12,6 @@ *
      
        * verify(mock, timeout(100).times(5)).foo();
        *
      - * verify(mock, timeout(100).never()).bar();
      - *
        * verify(mock, timeout(200).atLeastOnce()).baz();
        * 
      * diff --git a/src/test/java/org/mockito/MockitoEnvTest.java b/src/test/java/org/mockito/MockitoEnvTest.java new file mode 100644 index 0000000000..1a162e9390 --- /dev/null +++ b/src/test/java/org/mockito/MockitoEnvTest.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2021 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.CoreMatchers.endsWith; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.nullValue; + +import org.junit.Assume; +import org.junit.Test; +import org.mockito.internal.configuration.plugins.DefaultMockitoPlugins; +import org.mockito.internal.configuration.plugins.Plugins; +import org.mockito.plugins.MemberAccessor; +import org.mockito.plugins.MockMaker; + +public class MockitoEnvTest { + @Test + public void uses_default_mock_maker_from_env() { + final String mockMaker = System.getenv("MOCK_MAKER"); + Assume.assumeThat(mockMaker, not(nullValue())); + Assume.assumeThat(mockMaker, endsWith("default")); + + assertThat(DefaultMockitoPlugins.getDefaultPluginClass(MockMaker.class.getName())) + .isEqualTo(Plugins.getMockMaker().getClass().getName()); + } + + @Test + public void uses_mock_maker_from_env() { + final String mockMaker = System.getenv("MOCK_MAKER"); + Assume.assumeThat(mockMaker, not(nullValue())); + Assume.assumeThat(mockMaker, not(endsWith("default"))); + + assertThat(DefaultMockitoPlugins.getDefaultPluginClass(mockMaker)) + .isEqualTo(Plugins.getMockMaker().getClass().getName()); + } + + @Test + public void uses_default_member_accessor_from_env() { + final String memberAccessor = System.getenv("MEMBER_ACCESSOR"); + Assume.assumeThat(memberAccessor, not(nullValue())); + Assume.assumeThat(memberAccessor, endsWith("default")); + + assertThat(DefaultMockitoPlugins.getDefaultPluginClass(MemberAccessor.class.getName())) + .isEqualTo(Plugins.getMemberAccessor().getClass().getName()); + } + + @Test + public void uses_member_accessor_from_env() { + final String memberAccessor = System.getenv("MEMBER_ACCESSOR"); + Assume.assumeThat(memberAccessor, not(nullValue())); + Assume.assumeThat(memberAccessor, not(endsWith("default"))); + + assertThat(DefaultMockitoPlugins.getDefaultPluginClass(memberAccessor)) + .isEqualTo(Plugins.getMemberAccessor().getClass().getName()); + } +} diff --git a/src/test/java/org/mockito/MockitoTest.java b/src/test/java/org/mockito/MockitoTest.java index 8455011bb2..1671280d0c 100644 --- a/src/test/java/org/mockito/MockitoTest.java +++ b/src/test/java/org/mockito/MockitoTest.java @@ -4,18 +4,25 @@ */ package org.mockito; +import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.not; import static org.mockito.Mockito.times; import static org.mockito.internal.progress.ThreadSafeMockingProgress.mockingProgress; import java.util.List; +import org.junit.Assume; import org.junit.Test; import org.mockito.exceptions.base.MockitoException; import org.mockito.exceptions.misusing.NotAMockException; import org.mockito.exceptions.misusing.NullInsteadOfMockException; +import org.mockito.internal.configuration.plugins.Plugins; import org.mockito.internal.creation.MockSettingsImpl; +import org.mockito.listeners.InvocationListener; +import org.mockito.plugins.InlineMockMaker; @SuppressWarnings("unchecked") public class MockitoTest { @@ -98,6 +105,8 @@ public void shouldValidateMockWhenCreatingInOrderObject() { @SuppressWarnings({"CheckReturnValue", "MockitoUsage"}) @Test public void shouldGiveExplanationOnStaticMockingWithoutInlineMockMaker() { + Assume.assumeThat(Plugins.getMockMaker(), not(instanceOf(InlineMockMaker.class))); + assertThatThrownBy( () -> { Mockito.mockStatic(Object.class); @@ -113,6 +122,8 @@ public void shouldGiveExplanationOnStaticMockingWithoutInlineMockMaker() { @SuppressWarnings({"CheckReturnValue", "MockitoUsage"}) @Test public void shouldGiveExplanationOnConstructionMockingWithoutInlineMockMaker() { + Assume.assumeThat(Plugins.getMockMaker(), not(instanceOf(InlineMockMaker.class))); + assertThatThrownBy( () -> { Mockito.mockConstruction(Object.class); @@ -125,6 +136,20 @@ public void shouldGiveExplanationOnConstructionMockingWithoutInlineMockMaker() { "Note that Mockito's inline mock maker is not supported on Android."); } + @SuppressWarnings({"CheckReturnValue", "MockitoUsage"}) + @Test + public void shouldGiveExplanationOnConstructionMockingWithInlineMockMaker() { + Assume.assumeThat(Plugins.getMockMaker(), instanceOf(InlineMockMaker.class)); + + assertThatThrownBy( + () -> { + Mockito.mockConstruction(Object.class); + }) + .isInstanceOf(MockitoException.class) + .hasMessageContainingAll( + "It is not possible to mock construction of the Object class to avoid inference with default object constructor chains"); + } + @Test public void shouldStartingMockSettingsContainDefaultBehavior() { // given @@ -133,4 +158,86 @@ public void shouldStartingMockSettingsContainDefaultBehavior() { // when / then assertThat(settings.getDefaultAnswer()).isEqualTo(Mockito.RETURNS_DEFAULTS); } + + @Test + @SuppressWarnings({"DoNotMock", "DoNotMockAutoValue"}) + public void automaticallyDetectsClassToMock() { + List mock = Mockito.mock(); + Mockito.when(mock.size()).thenReturn(42); + assertThat(mock.size()).isEqualTo(42); + } + + @Test + @SuppressWarnings({"DoNotMock", "DoNotMockAutoValue"}) + public void newMockMethod_shouldNotBeCalledWithNullParameters() { + assertThatThrownBy( + () -> { + Mockito.mock((Object[]) null); + }) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageStartingWith("Please don't pass any values here"); + } + + @Test + public void reifiedMockMethodWithNameSetsTheExpectedName() { + List mock = Mockito.mock("My super cool new mock"); + assertThat(mock).hasToString("My super cool new mock"); + } + + @Test + public void reifiedMockMethodWithDefaultAnswerSetsTheDefaultAnswer() { + abstract class Something { + abstract Something somethingElse(); + } + + Something something = Mockito.mock(Answers.RETURNS_SELF); + + assertThat(something.somethingElse()).isSameAs(something); + } + + @Test + public void reifiedMockMethodWithSettingsAppliesTheSettings() { + + InvocationListener invocationListener = Mockito.mock(InvocationListener.class); + + List mock = + Mockito.mock( + Mockito.withSettings() + .name("my name here") + .invocationListeners(invocationListener)); + + assertThat(mock).hasToString("my name here"); + Mockito.verify(invocationListener).reportInvocation(ArgumentMatchers.any()); + } + + @Test + @SuppressWarnings({"DoNotMock", "DoNotMockAutoValue"}) + public void newMockMethod_shouldNotBeCalledWithParameters() { + assertThatThrownBy( + () -> { + Mockito.mock(asList("1", "2"), asList("3", "4")); + }) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageStartingWith("Please don't pass any values here"); + } + + @Test + @SuppressWarnings({"DoNotMock", "DoNotMockAutoValue"}) + public void automaticallyDetectsClassToSpy() { + List mock = Mockito.spy(); + Mockito.when(mock.size()).thenReturn(42); + assertThat(mock.size()).isEqualTo(42); + assertThat(mock.get(0)).isNull(); + } + + @Test + @SuppressWarnings({"DoNotMock", "DoNotMockAutoValue"}) + public void newSpyMethod_shouldNotBeCalledWithParameters() { + assertThatThrownBy( + () -> { + Mockito.spy(asList("1", "2"), asList("3", "4")); + }) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageStartingWith("Please don't pass any values here"); + } } diff --git a/src/test/java/org/mockito/internal/configuration/plugins/DefaultMockitoPluginsTest.java b/src/test/java/org/mockito/internal/configuration/plugins/DefaultMockitoPluginsTest.java index 44afe0b6a9..61fc8e8ed1 100644 --- a/src/test/java/org/mockito/internal/configuration/plugins/DefaultMockitoPluginsTest.java +++ b/src/test/java/org/mockito/internal/configuration/plugins/DefaultMockitoPluginsTest.java @@ -7,9 +7,9 @@ import static org.junit.Assert.*; import static org.mockito.internal.configuration.plugins.DefaultMockitoPlugins.INLINE_ALIAS; import static org.mockito.internal.configuration.plugins.DefaultMockitoPlugins.PROXY_ALIAS; +import static org.mockito.internal.configuration.plugins.DefaultMockitoPlugins.SUBCLASS_ALIAS; import org.junit.Test; -import org.mockito.internal.creation.bytebuddy.ByteBuddyMockMaker; import org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker; import org.mockito.internal.util.ConsoleMockitoLogger; import org.mockito.plugins.InstantiatorProvider2; @@ -25,13 +25,17 @@ public class DefaultMockitoPluginsTest extends TestBase { public void provides_plugins() throws Exception { assertEquals( "org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker", - plugins.getDefaultPluginClass(INLINE_ALIAS)); + DefaultMockitoPlugins.getDefaultPluginClass(INLINE_ALIAS)); assertEquals(InlineByteBuddyMockMaker.class, plugins.getInlineMockMaker().getClass()); assertEquals( "org.mockito.internal.creation.proxy.ProxyMockMaker", - plugins.getDefaultPluginClass(PROXY_ALIAS)); + DefaultMockitoPlugins.getDefaultPluginClass(PROXY_ALIAS)); assertEquals( - ByteBuddyMockMaker.class, plugins.getDefaultPlugin(MockMaker.class).getClass()); + "org.mockito.internal.creation.bytebuddy.ByteBuddyMockMaker", + DefaultMockitoPlugins.getDefaultPluginClass(SUBCLASS_ALIAS)); + assertEquals( + InlineByteBuddyMockMaker.class, + plugins.getDefaultPlugin(MockMaker.class).getClass()); assertNotNull(plugins.getDefaultPlugin(InstantiatorProvider2.class)); assertEquals( ConsoleMockitoLogger.class, diff --git a/src/test/java/org/mockito/internal/creation/bytebuddy/InlineDelegateByteBuddyMockMakerTest.java b/src/test/java/org/mockito/internal/creation/bytebuddy/InlineDelegateByteBuddyMockMakerTest.java index 3d42f3a377..89aa415a96 100644 --- a/src/test/java/org/mockito/internal/creation/bytebuddy/InlineDelegateByteBuddyMockMakerTest.java +++ b/src/test/java/org/mockito/internal/creation/bytebuddy/InlineDelegateByteBuddyMockMakerTest.java @@ -80,6 +80,22 @@ public void should_create_mock_from_final_spy() throws Exception { }); } + @Test + public void should_create_mock_from_accessible_inner_spy() throws Exception { + MockCreationSettings settings = settingsFor(Outer.Inner.class); + Optional proxy = + mockMaker.createSpy( + settings, + new MockHandlerImpl<>(settings), + new Outer.Inner(new Object(), new Object())); + assertThat(proxy) + .hasValueSatisfying( + spy -> { + assertThat(spy.p1).isNotNull(); + assertThat(spy.p2).isNotNull(); + }); + } + @Test public void should_create_mock_from_non_constructable_class() throws Exception { MockCreationSettings settings = @@ -646,4 +662,23 @@ void internalThrowException(int test) throws IOException { } } } + + static class Outer { + + final Object p1; + + private Outer(final Object p1) { + this.p1 = p1; + } + + private static class Inner extends Outer { + + final Object p2; + + Inner(final Object p1, final Object p2) { + super(p1); + this.p2 = p2; + } + } + } } diff --git a/src/test/java/org/mockito/internal/debugging/LoggingListenerTest.java b/src/test/java/org/mockito/internal/debugging/LoggingListenerTest.java index 54c5bec09d..a81aa98d2f 100644 --- a/src/test/java/org/mockito/internal/debugging/LoggingListenerTest.java +++ b/src/test/java/org/mockito/internal/debugging/LoggingListenerTest.java @@ -97,7 +97,7 @@ public void informs_about_various_kinds_of_stubs() { + "[Mockito]\n" + "[Mockito] 1. at com.FooTest:30\n" + "[Mockito]\n" - + "[Mockito] Unstubbed method invocations (perhaps missing stubbing in the test?):\n" + + "[Mockito] Un-stubbed method invocations (perhaps missing stubbing in the test?):\n" + "[Mockito]\n" + "[Mockito] 1. at com.Foo:96", listener.getStubbingInfo()); @@ -127,7 +127,7 @@ public void informs_about_unstubbed() { assertEquals( "[Mockito] Additional stubbing information (see javadoc for StubbingInfo class):\n" + "[Mockito]\n" - + "[Mockito] Unstubbed method invocations (perhaps missing stubbing in the test?):\n" + + "[Mockito] Un-stubbed method invocations (perhaps missing stubbing in the test?):\n" + "[Mockito]\n" + "[Mockito] 1. com.Foo:20", listener.getStubbingInfo()); diff --git a/src/test/java/org/mockito/internal/invocation/InvocationBuilder.java b/src/test/java/org/mockito/internal/invocation/InvocationBuilder.java index 9900db9d98..d5cf3e5577 100644 --- a/src/test/java/org/mockito/internal/invocation/InvocationBuilder.java +++ b/src/test/java/org/mockito/internal/invocation/InvocationBuilder.java @@ -13,7 +13,7 @@ import java.util.List; import org.mockito.Mockito; -import org.mockito.internal.debugging.LocationImpl; +import org.mockito.internal.debugging.LocationFactory; import org.mockito.internal.invocation.mockref.MockReference; import org.mockito.internal.invocation.mockref.MockStrongReference; import org.mockito.invocation.Invocation; @@ -72,7 +72,7 @@ public Invocation toInvocation() { new SerializableMethod(method), args, NO_OP, - location == null ? new LocationImpl() : location, + location == null ? LocationFactory.create() : location, 1); if (verified) { i.markVerified(); diff --git a/src/test/java/org/mockito/internal/invocation/InvocationMatcherTest.java b/src/test/java/org/mockito/internal/invocation/InvocationMatcherTest.java index 0b3d09d5f1..a217d4d8d3 100644 --- a/src/test/java/org/mockito/internal/invocation/InvocationMatcherTest.java +++ b/src/test/java/org/mockito/internal/invocation/InvocationMatcherTest.java @@ -136,7 +136,7 @@ public void should_be_similar_if_is_overloaded_but_used_with_different_arg() thr public void should_capture_arguments_from_invocation() throws Exception { // given Invocation invocation = new InvocationBuilder().args("1", 100).toInvocation(); - CapturingMatcher capturingMatcher = new CapturingMatcher(); + CapturingMatcher capturingMatcher = new CapturingMatcher(List.class); InvocationMatcher invocationMatcher = new InvocationMatcher(invocation, (List) asList(new Equals("1"), capturingMatcher)); @@ -149,11 +149,11 @@ public void should_capture_arguments_from_invocation() throws Exception { } @Test - public void should_match_varargs_using_any_varargs() throws Exception { + public void should_match_varargs_using_any_varargs() { // given mock.varargs("1", "2"); Invocation invocation = getLastInvocation(); - InvocationMatcher invocationMatcher = new InvocationMatcher(invocation, (List) asList(ANY)); + InvocationMatcher invocationMatcher = new InvocationMatcher(invocation, asList(ANY, ANY)); // when boolean match = invocationMatcher.matches(invocation); @@ -163,11 +163,11 @@ public void should_match_varargs_using_any_varargs() throws Exception { } @Test - public void should_capture_varargs_as_vararg() throws Exception { + public void should_capture_varargs_as_vararg() { // given mock.mixedVarargs(1, "a", "b"); Invocation invocation = getLastInvocation(); - CapturingMatcher m = new CapturingMatcher(); + CapturingMatcher m = new CapturingMatcher(String[].class); InvocationMatcher invocationMatcher = new InvocationMatcher(invocation, Arrays.asList(new Equals(1), m)); @@ -175,11 +175,11 @@ public void should_capture_varargs_as_vararg() throws Exception { invocationMatcher.captureArgumentsFrom(invocation); // then - Assertions.assertThat(m.getAllValues()).containsExactly("a", "b"); + Assertions.assertThat(m.getAllValues()).containsExactly(new String[] {"a", "b"}); } @Test // like using several time the captor in the vararg - public void should_capture_arguments_when_args_count_does_NOT_match() throws Exception { + public void should_capture_arguments_when_args_count_does_NOT_match() { // given mock.varargs(); Invocation invocation = getLastInvocation(); diff --git a/src/test/java/org/mockito/internal/invocation/MatcherApplicationStrategyTest.java b/src/test/java/org/mockito/internal/invocation/MatcherApplicationStrategyTest.java index 285bfecc66..21c94b1525 100644 --- a/src/test/java/org/mockito/internal/invocation/MatcherApplicationStrategyTest.java +++ b/src/test/java/org/mockito/internal/invocation/MatcherApplicationStrategyTest.java @@ -25,7 +25,6 @@ import org.mockito.internal.matchers.Any; import org.mockito.internal.matchers.Equals; import org.mockito.internal.matchers.InstanceOf; -import org.mockito.internal.matchers.VarargMatcher; import org.mockito.invocation.Invocation; import org.mockitousage.IMethods; import org.mockitoutil.TestBase; @@ -35,7 +34,7 @@ public class MatcherApplicationStrategyTest extends TestBase { @Mock IMethods mock; private Invocation invocation; - private List matchers; + private List> matchers; private RecordingAction recordAction; @@ -123,7 +122,7 @@ public void shouldKnowWhenVarargsMatch() { public void shouldAllowAnyMatchEntireVararg() { // given invocation = varargs("1", "2"); - matchers = asList(ANY); + matchers = asList(ANY, ANY); // when boolean match = @@ -150,10 +149,10 @@ public void shouldNotAllowAnyWithMixedVarargs() { } @Test - public void shouldAllowanyWithMixedVarargs() { + public void shouldAllowAnyWithMixedVarargs() { // given invocation = mixedVarargs(1, "1", "2"); - matchers = asList(new Equals(1), ANY); + matchers = asList(new Equals(1), ANY, ANY); // when boolean match = @@ -185,7 +184,7 @@ public void shouldAnyDealWithDifferentSizeOfArgs() { public void shouldMatchAnyEvenIfOneOfTheArgsIsNull() { // given invocation = mixedVarargs(null, null, "2"); - matchers = asList(new Equals(null), ANY); + matchers = asList(new Equals(null), ANY, ANY); // when getMatcherApplicationStrategyFor(invocation, matchers) @@ -199,7 +198,7 @@ public void shouldMatchAnyEvenIfOneOfTheArgsIsNull() { public void shouldMatchAnyEvenIfMatcherIsDecorated() { // given invocation = varargs("1", "2"); - matchers = asList(ANY); + matchers = asList(ANY, ANY); // when getMatcherApplicationStrategyFor(invocation, matchers) @@ -213,8 +212,9 @@ public void shouldMatchAnyEvenIfMatcherIsDecorated() { public void shouldMatchAnyEvenIfMatcherIsWrappedInHamcrestMatcher() { // given invocation = varargs("1", "2"); - HamcrestArgumentMatcher argumentMatcher = new HamcrestArgumentMatcher(new IntMatcher()); - matchers = asList(argumentMatcher); + HamcrestArgumentMatcher argumentMatcher = + new HamcrestArgumentMatcher<>(new IntMatcher()); + matchers = asList(argumentMatcher, argumentMatcher); // when getMatcherApplicationStrategyFor(invocation, matchers) @@ -224,7 +224,30 @@ public void shouldMatchAnyEvenIfMatcherIsWrappedInHamcrestMatcher() { recordAction.assertContainsExactly(argumentMatcher, argumentMatcher); } - class IntMatcher extends BaseMatcher implements VarargMatcher { + @Test + public void shouldMatchAnyThatMatchesRawVarArgType() { + // given + invocation = varargs("1", "2"); + InstanceOf any = new InstanceOf(String[].class, ""); + matchers = asList(any); + + // when + getMatcherApplicationStrategyFor(invocation, matchers) + .forEachMatcherAndArgument(recordAction); + + // then + recordAction.assertContainsExactly(any); + } + + private static class IntMatcher extends BaseMatcher { + public boolean matches(Object o) { + return true; + } + + public void describeTo(Description description) {} + } + + private static class IntArrayMatcher extends BaseMatcher { public boolean matches(Object o) { return true; } @@ -242,8 +265,8 @@ private Invocation varargs(String... s) { return getLastInvocation(); } - private class RecordingAction implements ArgumentMatcherAction { - private List> matchers = new ArrayList>(); + private static class RecordingAction implements ArgumentMatcherAction { + private final List> matchers = new ArrayList>(); @Override public boolean apply(ArgumentMatcher matcher, Object argument) { diff --git a/src/test/java/org/mockito/internal/matchers/AndTest.java b/src/test/java/org/mockito/internal/matchers/AndTest.java new file mode 100644 index 0000000000..3115576d9e --- /dev/null +++ b/src/test/java/org/mockito/internal/matchers/AndTest.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2022 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.internal.matchers; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentMatcher; +import org.mockito.Mock; +import org.mockitoutil.TestBase; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; + +public class AndTest extends TestBase { + + @Mock private ArgumentMatcher m1; + @Mock private ArgumentMatcher m2; + private And and; + + @Before + public void setUp() throws Exception { + and = new And(m1, m2); + } + + @Test + public void shouldReturnMatchingTypes() { + given(m1.type()).will(inv -> String.class); + given(m2.type()).will(inv -> String.class); + + assertThat(and.type()).isEqualTo(String.class); + } + + @Test + public void shouldDefaultMismatchingTypes() { + given(m1.type()).will(inv -> String.class); + given(m2.type()).will(inv -> Integer.class); + + assertThat(and.type()).isEqualTo(Void.class); + } + + @Test + public void shouldReturnLeftBaseType() { + given(m1.type()).will(inv -> BaseType.class); + given(m2.type()).will(inv -> SubType.class); + + assertThat(and.type()).isEqualTo(BaseType.class); + } + + @Test + public void shouldReturnRightBaseType() { + given(m1.type()).will(inv -> SubType.class); + given(m2.type()).will(inv -> BaseType.class); + + assertThat(and.type()).isEqualTo(BaseType.class); + } + + private interface BaseType {} + + private interface SubType extends BaseType {} +} diff --git a/src/test/java/org/mockito/internal/matchers/CapturingMatcherTest.java b/src/test/java/org/mockito/internal/matchers/CapturingMatcherTest.java index a4c6b59149..768d08d0b8 100644 --- a/src/test/java/org/mockito/internal/matchers/CapturingMatcherTest.java +++ b/src/test/java/org/mockito/internal/matchers/CapturingMatcherTest.java @@ -19,7 +19,7 @@ public class CapturingMatcherTest extends TestBase { @Test public void should_capture_arguments() throws Exception { // given - CapturingMatcher m = new CapturingMatcher(); + CapturingMatcher m = new CapturingMatcher(String.class); // when m.captureFrom("foo"); @@ -32,7 +32,7 @@ public void should_capture_arguments() throws Exception { @Test public void should_know_last_captured_value() throws Exception { // given - CapturingMatcher m = new CapturingMatcher(); + CapturingMatcher m = new CapturingMatcher(String.class); // when m.captureFrom("foo"); @@ -45,7 +45,7 @@ public void should_know_last_captured_value() throws Exception { @Test public void should_scream_when_nothing_yet_captured() throws Exception { // given - CapturingMatcher m = new CapturingMatcher(); + CapturingMatcher m = new CapturingMatcher(String.class); try { // when @@ -59,7 +59,7 @@ public void should_scream_when_nothing_yet_captured() throws Exception { @Test public void should_not_fail_when_used_in_concurrent_tests() throws Exception { // given - final CapturingMatcher m = new CapturingMatcher(); + final CapturingMatcher m = new CapturingMatcher(String.class); // when m.captureFrom("concurrent access"); diff --git a/src/test/java/org/mockito/internal/matchers/EqualsTest.java b/src/test/java/org/mockito/internal/matchers/EqualsTest.java index ba5c578b57..e56de2a9c2 100644 --- a/src/test/java/org/mockito/internal/matchers/EqualsTest.java +++ b/src/test/java/org/mockito/internal/matchers/EqualsTest.java @@ -4,9 +4,11 @@ */ package org.mockito.internal.matchers; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.*; import org.junit.Test; +import org.mockito.ArgumentMatcher; import org.mockitoutil.TestBase; public class EqualsTest extends TestBase { @@ -102,4 +104,15 @@ public void shouldMatchTypesSafelyWhenGivenIsNull() throws Exception { // then assertFalse(equals.typeMatches(null)); } + + @Test + public void shouldInferType() { + assertThat(new Equals("String").type()).isEqualTo(String.class); + } + + @Test + public void shouldDefaultTypeOnNull() { + assertThat(new Equals(null).type()) + .isEqualTo(((ArgumentMatcher) argument -> false).type()); + } } diff --git a/src/test/java/org/mockito/internal/matchers/InstanceOfTest.java b/src/test/java/org/mockito/internal/matchers/InstanceOfTest.java index 8addef8ca0..7740d0b77d 100644 --- a/src/test/java/org/mockito/internal/matchers/InstanceOfTest.java +++ b/src/test/java/org/mockito/internal/matchers/InstanceOfTest.java @@ -59,9 +59,8 @@ public void should_check_for_primitive_wrapper_types() { @Test public void can_be_vararg_aware() { - assertThat(new InstanceOf.VarArgAware(Number[].class)).isInstanceOf(VarargMatcher.class); - assertThat(new InstanceOf.VarArgAware(Number[].class).matches(new Integer[0])).isTrue(); - assertThat(new InstanceOf.VarArgAware(Number[].class).matches(new Number[0])).isTrue(); - assertThat(new InstanceOf.VarArgAware(Number[].class).matches(new Object[0])).isFalse(); + assertThat(new InstanceOf(Number[].class).matches(new Integer[0])).isTrue(); + assertThat(new InstanceOf(Number[].class).matches(new Number[0])).isTrue(); + assertThat(new InstanceOf(Number[].class).matches(new Object[0])).isFalse(); } } diff --git a/src/test/java/org/mockito/internal/matchers/OrTest.java b/src/test/java/org/mockito/internal/matchers/OrTest.java new file mode 100644 index 0000000000..6738d73507 --- /dev/null +++ b/src/test/java/org/mockito/internal/matchers/OrTest.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2022 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.internal.matchers; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentMatcher; +import org.mockito.Mock; +import org.mockitoutil.TestBase; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; + +public class OrTest extends TestBase { + + @Mock private ArgumentMatcher m1; + @Mock private ArgumentMatcher m2; + private Or or; + + @Before + public void setUp() throws Exception { + or = new Or(m1, m2); + } + + @Test + public void shouldReturnMatchingTypes() { + given(m1.type()).will(inv -> String.class); + given(m2.type()).will(inv -> String.class); + + assertThat(or.type()).isEqualTo(String.class); + } + + @Test + public void shouldDefaultMismatchingTypes() { + given(m1.type()).will(inv -> String.class); + given(m2.type()).will(inv -> Integer.class); + + assertThat(or.type()).isEqualTo(Void.class); + } + + @Test + public void shouldReturnLeftBaseType() { + given(m1.type()).will(inv -> BaseType.class); + given(m2.type()).will(inv -> SubType.class); + + assertThat(or.type()).isEqualTo(BaseType.class); + } + + @Test + public void shouldReturnRightBaseType() { + given(m1.type()).will(inv -> SubType.class); + given(m2.type()).will(inv -> BaseType.class); + + assertThat(or.type()).isEqualTo(BaseType.class); + } + + private interface BaseType {} + + private interface SubType extends BaseType {} +} diff --git a/src/test/java/org/mockito/internal/matchers/SameTest.java b/src/test/java/org/mockito/internal/matchers/SameTest.java new file mode 100644 index 0000000000..d5c6a1f9c8 --- /dev/null +++ b/src/test/java/org/mockito/internal/matchers/SameTest.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2022 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.internal.matchers; + +import org.mockitoutil.TestBase; +import org.junit.Test; +import org.mockito.ArgumentMatcher; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SameTest extends TestBase { + + @Test + public void shouldInferType() { + assertThat(new Same("String").type()).isEqualTo(String.class); + } + + @Test + public void shouldDefaultTypeOnNull() { + assertThat(new Same(null).type()) + .isEqualTo(((ArgumentMatcher) argument -> false).type()); + } +} diff --git a/src/test/java/org/mockito/internal/runners/DefaultInternalRunnerTest.java b/src/test/java/org/mockito/internal/runners/DefaultInternalRunnerTest.java index c8912ec223..0e4e40076e 100644 --- a/src/test/java/org/mockito/internal/runners/DefaultInternalRunnerTest.java +++ b/src/test/java/org/mockito/internal/runners/DefaultInternalRunnerTest.java @@ -4,11 +4,14 @@ */ package org.mockito.internal.runners; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.not; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; +import org.junit.Assume; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestRule; @@ -18,9 +21,11 @@ import org.junit.runner.notification.RunNotifier; import org.junit.runners.model.Statement; import org.mockito.Mock; +import org.mockito.internal.configuration.plugins.Plugins; import org.mockito.internal.junit.MockitoTestListener; import org.mockito.internal.junit.TestFinishedEvent; import org.mockito.internal.util.Supplier; +import org.mockito.plugins.InlineMockMaker; public class DefaultInternalRunnerTest { @@ -42,6 +47,9 @@ public void does_not_fail_when_tests_succeeds() throws Exception { @Test public void does_not_fail_second_test_when_first_test_fail() throws Exception { + // The TestFailOnInitialization is initialized properly by inline mock maker + Assume.assumeThat(Plugins.getMockMaker(), not(instanceOf(InlineMockMaker.class))); + new DefaultInternalRunner(TestFailOnInitialization.class, supplier) .run(newNotifier(runListener)); diff --git a/src/test/java/org/mockito/internal/stubbing/defaultanswers/ReturnsSmartNullsTest.java b/src/test/java/org/mockito/internal/stubbing/defaultanswers/ReturnsSmartNullsTest.java index 984e07da2a..267c6d6966 100644 --- a/src/test/java/org/mockito/internal/stubbing/defaultanswers/ReturnsSmartNullsTest.java +++ b/src/test/java/org/mockito/internal/stubbing/defaultanswers/ReturnsSmartNullsTest.java @@ -20,7 +20,7 @@ import org.assertj.core.api.ThrowableAssert; import org.junit.Test; import org.mockito.exceptions.verification.SmartNullPointerException; -import org.mockito.internal.debugging.LocationImpl; +import org.mockito.internal.debugging.LocationFactory; import org.mockito.internal.invocation.InterceptedInvocation; import org.mockito.internal.invocation.SerializableMethod; import org.mockito.internal.invocation.mockref.MockStrongReference; @@ -146,7 +146,7 @@ private static InterceptedInvocation invocationMethodWithArgs(final T obj) GenericFooBar.class.getMethod("methodWithArgs", int.class, Object.class)), new Object[] {1, obj}, InterceptedInvocation.NO_OP, - new LocationImpl(), + LocationFactory.create(), 1); } @@ -269,7 +269,7 @@ private static InterceptedInvocation invocationMethodWithVarArgs(final T[] o "methodWithVarArgs", int.class, Object[].class)), new Object[] {1, obj}, InterceptedInvocation.NO_OP, - new LocationImpl(), + LocationFactory.create(), 1); } diff --git a/src/test/java/org/mockito/internal/util/MockCreationValidatorTest.java b/src/test/java/org/mockito/internal/util/MockCreationValidatorTest.java index 7efffcf6a2..7491a3f070 100644 --- a/src/test/java/org/mockito/internal/util/MockCreationValidatorTest.java +++ b/src/test/java/org/mockito/internal/util/MockCreationValidatorTest.java @@ -66,7 +66,7 @@ public void should_validation_be_safe_when_nulls_passed() { @Test public void should_fail_when_type_not_mockable() { try { - validator.validateType(long.class); + validator.validateType(long.class, null); } catch (MockitoException ex) { assertThat(ex.getMessage()).contains("primitive"); } diff --git a/src/test/java/org/mockito/internal/util/MockUtilTest.java b/src/test/java/org/mockito/internal/util/MockUtilTest.java index 50cadb4c0c..834178cdec 100644 --- a/src/test/java/org/mockito/internal/util/MockUtilTest.java +++ b/src/test/java/org/mockito/internal/util/MockUtilTest.java @@ -100,12 +100,12 @@ interface SomeInterface {} @Test public void should_know_if_type_is_mockable() throws Exception { - Assertions.assertThat(MockUtil.typeMockabilityOf(FinalClass.class).mockable()) + Assertions.assertThat(MockUtil.typeMockabilityOf(FinalClass.class, null).mockable()) .isEqualTo(Plugins.getMockMaker().isTypeMockable(FinalClass.class).mockable()); - assertFalse(MockUtil.typeMockabilityOf(int.class).mockable()); + assertFalse(MockUtil.typeMockabilityOf(int.class, null).mockable()); - assertTrue(MockUtil.typeMockabilityOf(SomeClass.class).mockable()); - assertTrue(MockUtil.typeMockabilityOf(SomeInterface.class).mockable()); + assertTrue(MockUtil.typeMockabilityOf(SomeClass.class, null).mockable()); + assertTrue(MockUtil.typeMockabilityOf(SomeInterface.class, null).mockable()); } } diff --git a/src/test/java/org/mockito/internal/util/PlatformTest.java b/src/test/java/org/mockito/internal/util/PlatformTest.java index d4c453386d..42f59a1bc6 100644 --- a/src/test/java/org/mockito/internal/util/PlatformTest.java +++ b/src/test/java/org/mockito/internal/util/PlatformTest.java @@ -63,34 +63,6 @@ public void should_warn_for_jvm() throws Exception { .isEqualTo(""); } - @Test - public void should_parse_open_jdk_string_and_report_wether_below_or_nut_update_45() { - // Given - // Sources : - // - https://www.oracle.com/java/technologies/javase/versioning-naming.html - // - https://www.oracle.com/java/technologies/javase/jdk7-naming.html - // - https://www.oracle.com/java/technologies/javase/jdk8-naming.html - // - - // https://stackoverflow.com/questions/35844985/how-do-we-get-sr-and-fp-of-ibm-jre-using-java - // - - // https://www.ibm.com/support/knowledgecenter/SSYKE2_8.0.0/com.ibm.java.80.doc/user/build_number.html - Map versions = new HashMap<>(); - versions.put("1.8.0_92-b14", false); - versions.put("1.8.0-b24", true); - versions.put("1.8.0_5", true); - versions.put("1.8.0b5_u44", true); - versions.put("1.8.0b5_u92", false); - versions.put("1.7.0_4", false); - versions.put("1.4.0_03-b04", false); - versions.put("1.4.0_03-ea-b01", false); - versions.put("pxi3270_27sr4-20160303_03 (SR4)", false); - versions.put("pwi3260sr11-20120412_01 (SR11)", false); - versions.put("pwa6480sr1fp10-20150711_01 (SR1 FP10)", false); - versions.put("null", false); - - assertPlatformParsesCorrectlyVariousVersionScheme(versions); - } - @Test public void should_parse_open_jdk9_string() { // The tested method targets Java 8 but should be able to parse other Java version numbers @@ -140,9 +112,7 @@ public void should_parse_open_jdk9_string() { private void assertPlatformParsesCorrectlyVariousVersionScheme(Map versions) { for (Map.Entry version : versions.entrySet()) { - assertThat(Platform.isJava8BelowUpdate45(version.getKey())) - .describedAs(version.getKey()) - .isEqualTo(version.getValue()); + assertThat(version.getValue()).describedAs(version.getKey()).isEqualTo(false); } } } diff --git a/src/test/java/org/mockito/internal/util/reflection/ParameterizedConstructorInstantiatorTest.java b/src/test/java/org/mockito/internal/util/reflection/ParameterizedConstructorInstantiatorTest.java index 9e63e8df69..45ff5ea86f 100644 --- a/src/test/java/org/mockito/internal/util/reflection/ParameterizedConstructorInstantiatorTest.java +++ b/src/test/java/org/mockito/internal/util/reflection/ParameterizedConstructorInstantiatorTest.java @@ -73,7 +73,7 @@ public void should_be_created_with_an_argument_resolver() throws Exception { public void should_instantiate_type_if_resolver_provide_matching_types() throws Exception { Observer observer = mock(Observer.class); Map map = mock(Map.class); - given(resolver.resolveTypeInstances(ArgumentMatchers.[]>any())) + given(resolver.resolveTypeInstances(ArgumentMatchers.any(Class[].class))) .willReturn(new Object[] {observer, map}); new ParameterizedConstructorInstantiator(this, field("withMultipleConstructor"), resolver) @@ -120,7 +120,7 @@ this, field("withThrowingConstructor"), resolver) @Test public void should_instantiate_type_with_vararg_constructor() throws Exception { Observer[] vararg = new Observer[] {}; - given(resolver.resolveTypeInstances(ArgumentMatchers.[]>any())) + given(resolver.resolveTypeInstances(ArgumentMatchers.any(Class[].class))) .willReturn(new Object[] {"", vararg}); new ParameterizedConstructorInstantiator(this, field("withVarargConstructor"), resolver) diff --git a/src/test/java/org/mockito/internal/verification/argumentmatching/ArgumentMatchingToolTest.java b/src/test/java/org/mockito/internal/verification/argumentmatching/ArgumentMatchingToolTest.java index 5f55faf3b2..27c986f43f 100644 --- a/src/test/java/org/mockito/internal/verification/argumentmatching/ArgumentMatchingToolTest.java +++ b/src/test/java/org/mockito/internal/verification/argumentmatching/ArgumentMatchingToolTest.java @@ -17,7 +17,7 @@ import org.mockito.internal.matchers.Equals; import org.mockitoutil.TestBase; -@SuppressWarnings({"unchecked", "serial"}) +@SuppressWarnings({"rawtypes", "unchecked", "serial"}) public class ArgumentMatchingToolTest extends TestBase { @Test @@ -97,7 +97,6 @@ public void shouldWorkFineWhenGivenArgIsNull() { } @Test - @SuppressWarnings("rawtypes") public void shouldUseMatchersSafely() { // This matcher is evil cause typeMatches(Object) returns true for every passed type but // matches(T) @@ -137,4 +136,49 @@ public Object getWanted() { // then assertEquals(0, suspicious.length); } + + @Test + public void shouldNotFindNonMatchingIndexesWhenEveryArgsMatch() { + String arg1Value = "arg1"; + Integer arg2Value = 2222; + + Equals arg1 = new Equals(arg1Value); + Equals arg2 = new Equals(arg2Value); + + List indexes = + ArgumentMatchingTool.getNotMatchingArgsIndexes( + Arrays.asList(arg1, arg2), new Object[] {arg1Value, arg2Value}); + + assertEquals(Arrays.asList(), indexes); + } + + @Test + public void shouldFindNonMatchingIndexesWhenSingleArgDoesNotMatch() { + String arg1Value = "arg1"; + Integer arg2Value = 2222; + + Equals arg1 = new Equals(arg1Value); + Equals arg2 = new Equals(1111); + + List indexes = + ArgumentMatchingTool.getNotMatchingArgsIndexes( + Arrays.asList(arg1, arg2), new Object[] {arg1Value, arg2Value}); + + assertEquals(Arrays.asList(1), indexes); + } + + @Test + public void shouldFindNonMatchingIndexesWhenMultiArgsDoNotMatch() { + String arg1Value = "arg1"; + Integer arg2Value = 2222; + + Equals arg1 = new Equals("differs"); + Equals arg2 = new Equals(1111); + + List indexes = + ArgumentMatchingTool.getNotMatchingArgsIndexes( + Arrays.asList(arg1, arg2), new Object[] {arg1Value, arg2Value}); + + assertEquals(Arrays.asList(0, 1), indexes); + } } diff --git a/src/test/java/org/mockito/internal/verification/checkers/MissingInvocationCheckerTest.java b/src/test/java/org/mockito/internal/verification/checkers/MissingInvocationCheckerTest.java index 911a9a59ac..cd7da33ca2 100644 --- a/src/test/java/org/mockito/internal/verification/checkers/MissingInvocationCheckerTest.java +++ b/src/test/java/org/mockito/internal/verification/checkers/MissingInvocationCheckerTest.java @@ -95,6 +95,76 @@ public void shouldReportUsingInvocationDescription() { "mock.intArgumentMethod(MyCoolPrint(1111));"); } + @Test + public void shouldSpecifyPosition0WhenWantedInvocationDiffersFromActual() { + wanted = buildMultiArgsMethod().args("arg1", 2222).toInvocationMatcher(); + invocations = singletonList(buildMultiArgsMethod().args("differs", 2222).toInvocation()); + + assertThatThrownBy( + () -> { + MissingInvocationChecker.checkMissingInvocation(invocations, wanted); + }) + .isInstanceOf(ArgumentsAreDifferent.class) + .hasMessageContainingAll( + "Argument(s) are different! Wanted:", + "mock.simpleMethod(\"arg1\", 2222);", + "Actual invocations have different arguments at position [0]:", + "mock.simpleMethod(\"differs\", 2222);"); + } + + @Test + public void shouldSpecifyPosition1WhenWantedInvocationDiffersFromActual() { + wanted = buildMultiArgsMethod().args("arg1", 2222).toInvocationMatcher(); + invocations = singletonList(buildMultiArgsMethod().args("arg1", 1111).toInvocation()); + + assertThatThrownBy( + () -> { + MissingInvocationChecker.checkMissingInvocation(invocations, wanted); + }) + .isInstanceOf(ArgumentsAreDifferent.class) + .hasMessageContainingAll( + "Argument(s) are different! Wanted:", + "mock.simpleMethod(\"arg1\", 2222);", + "Actual invocations have different arguments at position [1]:", + "mock.simpleMethod(\"arg1\", 1111);"); + } + + @Test + public void shouldSpecifyPosition0And1WhenWantedInvocationDiffersFromActual() { + wanted = buildMultiArgsMethod().args("arg1", 2222).toInvocationMatcher(); + invocations = singletonList(buildMultiArgsMethod().args("differs", 1111).toInvocation()); + + assertThatThrownBy( + () -> { + MissingInvocationChecker.checkMissingInvocation(invocations, wanted); + }) + .isInstanceOf(ArgumentsAreDifferent.class) + .hasMessageContainingAll( + "Argument(s) are different! Wanted:", + "mock.simpleMethod(\"arg1\", 2222);", + "Actual invocations have different arguments at positions [0, 1]:", + "mock.simpleMethod(\"differs\", 1111);"); + } + + @Test + public void shouldNotSpecifyPositionWhenWantedSingleArgInvocationSiffersFromActual() { + wanted = buildIntArgMethod(new CustomInvocationBuilder()).arg(2222).toInvocationMatcher(); + invocations = + singletonList( + buildIntArgMethod(new CustomInvocationBuilder()).arg(1111).toInvocation()); + + assertThatThrownBy( + () -> { + MissingInvocationChecker.checkMissingInvocation(invocations, wanted); + }) + .isInstanceOf(ArgumentsAreDifferent.class) + .hasMessageContainingAll( + "Argument(s) are different! Wanted:", + "mock.intArgumentMethod(MyCoolPrint(2222));", + "Actual invocations have different arguments:", + "mock.intArgumentMethod(MyCoolPrint(1111));"); + } + private InvocationBuilder buildIntArgMethod(InvocationBuilder invocationBuilder) { return invocationBuilder.mock(mock).method("intArgumentMethod").argTypes(int.class); } @@ -107,6 +177,13 @@ private InvocationBuilder buildDifferentMethod() { return new InvocationBuilder().mock(mock).differentMethod(); } + private InvocationBuilder buildMultiArgsMethod() { + return new InvocationBuilder() + .mock(mock) + .method("simpleMethod") + .argTypes(String.class, Integer.class); + } + static class CustomInvocationBuilder extends InvocationBuilder { @Override protected Invocation createInvocation( diff --git a/src/test/java/org/mockitointegration/ClassLoadabilityChecker.java b/src/test/java/org/mockitointegration/ClassLoadabilityChecker.java new file mode 100644 index 0000000000..d19c51fb74 --- /dev/null +++ b/src/test/java/org/mockitointegration/ClassLoadabilityChecker.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2017 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockitointegration; + +import java.util.HashSet; +import java.util.Set; + +/** + * Check that classes can be loaded and initialized on a provided classloader. Used + * for checking that Mockito has no dependency on libraries like JUnit. + *

      + * Some classes are excluded from this checking - namely, classes that fail due to + * the absence of Java classes. It's assumed that this is due to a specific optional + * dependency on APIs available in certain Java versions and so other elements of the + * test Matrix will check that those classes do not depend on JUnit or ByteBuddy. We + * exclude based on the failure of a ClassNotFoundException, or a NoClassDefFoundError + * caused by the failing to load of a failing parent class. + */ +public final class ClassLoadabilityChecker { + private static final boolean INITIALIZE_CLASSES = true; + private final Set excludedClasses = new HashSet<>(); + private final ClassLoader classLoader; + private final String purpose; + + public ClassLoadabilityChecker(ClassLoader classLoader, String purpose) { + this.classLoader = classLoader; + this.purpose = purpose; + } + + public void checkLoadability(String className) { + try { + Class.forName(className, INITIALIZE_CLASSES, classLoader); + } catch (ClassNotFoundException | LinkageError e) { + if (isFailureExcluded(className, e)) { + return; + } + e.printStackTrace(); + throw new AssertionError( + String.format("'%s' has some dependency to %s", className, purpose)); + } + } + + private boolean isFailureExcluded(String loadedClass, Throwable thrown) { + if (thrown == null) { + return false; + } + if (thrown instanceof ClassNotFoundException) { + ClassNotFoundException cnf = (ClassNotFoundException) thrown; + if (cnf.getMessage().startsWith("java.")) { + excludedClasses.add(loadedClass); + return true; + } + } else if (thrown instanceof NoClassDefFoundError) { + NoClassDefFoundError ncdf = (NoClassDefFoundError) thrown; + // if Foo fails due to depending on a Java class, Foo$Bar will fail with a NCDFE + int lastInnerClass = loadedClass.lastIndexOf('$'); + if (lastInnerClass != -1) { + String parent = loadedClass.substring(0, lastInnerClass); + if (excludedClasses.contains(parent) && ncdf.getMessage().contains(parent)) { + excludedClasses.add(loadedClass); + return true; + } + } + } + + return isFailureExcluded(loadedClass, thrown.getCause()); + } +} diff --git a/src/test/java/org/mockitointegration/DeferMockMakersClassLoadingTest.java b/src/test/java/org/mockitointegration/DeferMockMakersClassLoadingTest.java new file mode 100644 index 0000000000..63c0d761bd --- /dev/null +++ b/src/test/java/org/mockitointegration/DeferMockMakersClassLoadingTest.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2019 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockitointegration; + +import static org.mockito.Mockito.withSettings; +import static org.mockitoutil.ClassLoaders.coverageTool; + +import java.lang.reflect.Method; + +import org.assertj.core.api.Assertions; +import org.hamcrest.Matcher; +import org.junit.Test; +import org.mockito.Mockito; +import org.mockito.internal.creation.bytebuddy.ByteBuddyMockMaker; +import org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker; +import org.mockito.internal.creation.bytebuddy.SubclassByteBuddyMockMaker; +import org.mockito.internal.creation.proxy.ProxyMockMaker; +import org.mockito.invocation.MockHandler; +import org.mockito.mock.MockCreationSettings; +import org.mockito.plugins.MockMaker; +import org.mockitoutil.ClassLoaders; + +public class DeferMockMakersClassLoadingTest { + private static final Object MY_MOCK = new Object(); + + @Test + public void mockito_should_not_load_mock_makers_it_does_not_need() throws Exception { + ClassLoader classLoader_without_mockMakers = + ClassLoaders.excludingClassLoader() + .withCodeSourceUrlOf( + Mockito.class, + Matcher.class, + CustomMockMaker.class, + Assertions.class) + .withCodeSourceUrlOf(coverageTool()) + .without( + ByteBuddyMockMaker.class.getName(), + SubclassByteBuddyMockMaker.class.getName(), + InlineByteBuddyMockMaker.class.getName(), + ProxyMockMaker.class.getName()) + .build(); + + ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(classLoader_without_mockMakers); + try { + Class self = classLoader_without_mockMakers.loadClass(getClass().getName()); + Method createMock = self.getMethod("createMock"); + createMock.invoke(null); + } finally { + Thread.currentThread().setContextClassLoader(contextClassLoader); + } + } + + // Called by reflection from the test method + public static void createMock() { + Assertions.assertThat( + Mockito.mock( + Object.class, + withSettings().mockMaker(CustomMockMaker.class.getName()))) + .isSameAs(MY_MOCK); + } + + public static class CustomMockMaker implements MockMaker { + @Override + public T createMock(MockCreationSettings settings, MockHandler handler) { + return settings.getTypeToMock().cast(MY_MOCK); + } + + @Override + public MockHandler getHandler(Object mock) { + throw new UnsupportedOperationException(); + } + + @Override + public void resetMock(Object mock, MockHandler newHandler, MockCreationSettings settings) { + throw new UnsupportedOperationException(); + } + + @Override + public TypeMockability isTypeMockable(Class type) { + return new TypeMockability() { + @Override + public boolean mockable() { + return type.equals(Object.class); + } + + @Override + public String nonMockableReason() { + return mockable() ? "" : "type != Object.class"; + } + }; + } + } +} diff --git a/src/test/java/org/mockitointegration/NoByteCodeDependenciesTest.java b/src/test/java/org/mockitointegration/NoByteCodeDependenciesTest.java index 3a2908dc1c..3db1395066 100644 --- a/src/test/java/org/mockitointegration/NoByteCodeDependenciesTest.java +++ b/src/test/java/org/mockitointegration/NoByteCodeDependenciesTest.java @@ -43,21 +43,11 @@ public void pure_mockito_should_not_depend_bytecode_libraries() throws Exception pureMockitoAPIClasses.remove( "org.mockito.internal.util.reflection.InstrumentationMemberAccessor"); + ClassLoadabilityChecker checker = + new ClassLoadabilityChecker( + classLoader_without_bytecode_libraries, "ByteBuddy or Objenesis"); for (String pureMockitoAPIClass : pureMockitoAPIClasses) { - checkDependency(classLoader_without_bytecode_libraries, pureMockitoAPIClass); - } - } - - private void checkDependency(ClassLoader classLoader, String pureMockitoAPIClass) - throws ClassNotFoundException { - try { - Class.forName(pureMockitoAPIClass, true, classLoader); - } catch (Throwable e) { - e.printStackTrace(); - throw new AssertionError( - String.format( - "'%s' has some dependency to Byte Buddy or Objenesis", - pureMockitoAPIClass)); + checker.checkLoadability(pureMockitoAPIClass); } } } diff --git a/src/test/java/org/mockitointegration/NoJUnitDependenciesTest.java b/src/test/java/org/mockitointegration/NoJUnitDependenciesTest.java index 7b156f0aa3..503d859617 100644 --- a/src/test/java/org/mockitointegration/NoJUnitDependenciesTest.java +++ b/src/test/java/org/mockitointegration/NoJUnitDependenciesTest.java @@ -42,27 +42,18 @@ public void pure_mockito_should_not_depend_JUnit___ByteBuddy() throws Exception .omit("runners", "junit", "JUnit", "opentest4j") .listOwnedClasses(); + ClassLoadabilityChecker checker = + new ClassLoadabilityChecker(classLoader_without_JUnit, "JUnit"); + // The later class is required to be initialized before any inline mock maker classes can be // loaded. - checkDependency( - classLoader_without_JUnit, + checker.checkLoadability( "org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker"); pureMockitoAPIClasses.remove( "org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker"); for (String pureMockitoAPIClass : pureMockitoAPIClasses) { - checkDependency(classLoader_without_JUnit, pureMockitoAPIClass); - } - } - - private void checkDependency(ClassLoader classLoader_without_JUnit, String pureMockitoAPIClass) - throws ClassNotFoundException { - try { - Class.forName(pureMockitoAPIClass, true, classLoader_without_JUnit); - } catch (Throwable e) { - e.printStackTrace(); - throw new AssertionError( - String.format("'%s' has some dependency to JUnit", pureMockitoAPIClass)); + checker.checkLoadability(pureMockitoAPIClass); } } } diff --git a/src/test/java/org/mockitousage/IMethods.java b/src/test/java/org/mockitousage/IMethods.java index 06492f09cb..5c1d3d8e5d 100644 --- a/src/test/java/org/mockitousage/IMethods.java +++ b/src/test/java/org/mockitousage/IMethods.java @@ -187,10 +187,12 @@ String sixArgumentVarArgsMethod( int varargs(Object... object); - String varargsReturningString(Object... object); - int varargs(String... string); + int polyVararg(BaseType... args); + + String varargsReturningString(Object... object); + void mixedVarargs(Object i, String... string); String mixedVarargsReturningString(Object i, String... string); @@ -199,6 +201,10 @@ String sixArgumentVarArgsMethod( Object[] mixedVarargsReturningObjectArray(Object i, String... string); + String methodWithVarargAndNonVarargVariants(String string); + + String methodWithVarargAndNonVarargVariants(String... string); + List listReturningMethod(Object... objects); LinkedList linkedListReturningMethod(); @@ -306,4 +312,6 @@ public int hashCode() { return Objects.hash(value); } } + + interface BaseType {} } diff --git a/src/test/java/org/mockitousage/MethodsImpl.java b/src/test/java/org/mockitousage/MethodsImpl.java index e2d53ba7ba..678d3f7fe1 100644 --- a/src/test/java/org/mockitousage/MethodsImpl.java +++ b/src/test/java/org/mockitousage/MethodsImpl.java @@ -354,14 +354,19 @@ public int varargs(Object... object) { return -1; } - public String varargsReturningString(Object... object) { - return null; - } - public int varargs(String... string) { return -1; } + @Override + public int polyVararg(final BaseType... args) { + return 0; + } + + public String varargsReturningString(Object... object) { + return null; + } + public void mixedVarargs(Object i, String... string) {} public String mixedVarargsReturningString(Object i, String... string) { @@ -376,6 +381,16 @@ public Object[] mixedVarargsReturningObjectArray(Object i, String... string) { return null; } + @Override + public String methodWithVarargAndNonVarargVariants(String string) { + return "plain"; + } + + @Override + public String methodWithVarargAndNonVarargVariants(String... string) { + return "varargs"; + } + public void varargsbyte(byte... bytes) {} public List listReturningMethod(Object... objects) { diff --git a/src/test/java/org/mockitousage/annotation/AnnotationsTest.java b/src/test/java/org/mockitousage/annotation/AnnotationsTest.java index 1a33f933b2..939888a73d 100644 --- a/src/test/java/org/mockitousage/annotation/AnnotationsTest.java +++ b/src/test/java/org/mockitousage/annotation/AnnotationsTest.java @@ -88,6 +88,9 @@ public void shouldLookForAnnotatedMocksInSuperClasses() throws Exception { @Mock(stubOnly = true) IMethods stubOnly; + @Mock(withoutAnnotations = true) + IMethods withoutAnnotations; + @Test public void shouldInitMocksWithGivenSettings() throws Exception { assertEquals("i have a name", namedAndReturningMocks.toString()); @@ -99,6 +102,11 @@ public void shouldInitMocksWithGivenSettings() throws Exception { assertTrue(hasExtraInterfaces instanceof List); assertTrue(Mockito.mockingDetails(stubOnly).getMockCreationSettings().isStubOnly()); + assertTrue( + Mockito.mockingDetails(withoutAnnotations) + .getMockCreationSettings() + .isStripAnnotations()); + assertEquals(0, noExtraConfig.intReturningMethod()); } diff --git a/src/test/java/org/mockitousage/annotation/SpyAnnotationTest.java b/src/test/java/org/mockitousage/annotation/SpyAnnotationTest.java index 01edd3c687..daa71bed33 100644 --- a/src/test/java/org/mockitousage/annotation/SpyAnnotationTest.java +++ b/src/test/java/org/mockitousage/annotation/SpyAnnotationTest.java @@ -6,6 +6,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.not; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -22,6 +24,7 @@ import java.util.LinkedList; import java.util.List; +import org.junit.Assume; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -30,6 +33,8 @@ import org.mockito.MockitoAnnotations; import org.mockito.Spy; import org.mockito.exceptions.base.MockitoException; +import org.mockito.internal.configuration.plugins.Plugins; +import org.mockito.plugins.InlineMockMaker; import org.mockitoutil.TestBase; @SuppressWarnings("unused") @@ -188,8 +193,21 @@ class WithSpy { } } + @Test + public void should_spy_private_inner() throws Exception { + Assume.assumeThat(Plugins.getMockMaker(), instanceOf(InlineMockMaker.class)); + + WithInnerPrivate inner = new WithInnerPrivate(); + MockitoAnnotations.openMocks(inner); + + when(inner.spy_field.lenght()).thenReturn(10); + assertEquals(10, inner.spy_field.lenght()); + } + @Test public void should_report_private_inner_not_supported() throws Exception { + Assume.assumeThat(Plugins.getMockMaker(), not(instanceOf(InlineMockMaker.class))); + try { MockitoAnnotations.openMocks(new WithInnerPrivate()); fail(); @@ -287,7 +305,11 @@ private class InnerPrivateConcrete extends InnerPrivateAbstract {} static class WithInnerPrivate { @Spy private InnerPrivate spy_field; - private class InnerPrivate {} + private class InnerPrivate { + int lenght() { + return 0; + } + } private class InnerPrivateSub extends InnerPrivate {} } diff --git a/src/test/java/org/mockitousage/basicapi/UsingVarargsTest.java b/src/test/java/org/mockitousage/basicapi/UsingVarargsTest.java index 551c63790a..8d69a2a52f 100644 --- a/src/test/java/org/mockitousage/basicapi/UsingVarargsTest.java +++ b/src/test/java/org/mockitousage/basicapi/UsingVarargsTest.java @@ -173,9 +173,9 @@ public void shouldStubCorrectlyWhenDoubleStringAndMixedVarargsUsed() { @Test // See bug #157 - public void shouldMatchEasilyEmptyVararg() throws Exception { + public void shouldMatchEasilyEmptyVararg() { // when - when(mock.foo(any())).thenReturn(-1); + when(mock.foo(any(Object[].class))).thenReturn(-1); // then assertEquals(-1, mock.foo()); diff --git a/src/test/java/org/mockitousage/bugs/ThreadLocalTest.java b/src/test/java/org/mockitousage/bugs/ThreadLocalTest.java new file mode 100644 index 0000000000..8c113d90a6 --- /dev/null +++ b/src/test/java/org/mockitousage/bugs/ThreadLocalTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockitousage.bugs; + +import static org.mockito.Mockito.RETURNS_MOCKS; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; + +import org.assertj.core.api.Assertions; +import org.junit.Test; +import org.mockitoutil.TestBase; + +/** + * This was an issue reported in #2905. Mocking {@link ThreadLocal} or classes extending {@link ThreadLocal} was + * throwing a {@link StackOverflowError}. + */ +public class ThreadLocalTest extends TestBase { + + @Test + public void mock_ThreadLocal_does_not_raise_StackOverflowError() { + StackOverflowError stackOverflowError = + Assertions.catchThrowableOfType( + () -> { + mock(ThreadLocal.class, RETURNS_MOCKS); + }, + StackOverflowError.class); + Assertions.assertThat(stackOverflowError).isNull(); + } + + @Test + public void mock_class_extending_ThreadLocal_does_not_raise_StackOverflowError() { + StackOverflowError stackOverflowError = + Assertions.catchThrowableOfType( + () -> { + mock(SomeThreadLocal.class, RETURNS_MOCKS); + }, + StackOverflowError.class); + Assertions.assertThat(stackOverflowError).isNull(); + } + + @Test + public void spy_ThreadLocal_does_not_raise_StackOverflowError() { + StackOverflowError stackOverflowError = + Assertions.catchThrowableOfType( + () -> { + spy(ThreadLocal.class); + }, + StackOverflowError.class); + Assertions.assertThat(stackOverflowError).isNull(); + } + + @Test + public void spy_class_extending_ThreadLocal_does_not_raise_StackOverflowError() { + StackOverflowError stackOverflowError = + Assertions.catchThrowableOfType( + () -> { + spy(SomeThreadLocal.class); + }, + StackOverflowError.class); + Assertions.assertThat(stackOverflowError).isNull(); + } + + static class SomeThreadLocal extends ThreadLocal {} +} diff --git a/src/test/java/org/mockitousage/bugs/varargs/VarargsAndAnyPicksUpExtraInvocationsTest.java b/src/test/java/org/mockitousage/bugs/varargs/VarargsAndAnyPicksUpExtraInvocationsTest.java index f6f4417c4a..c08bdf0643 100644 --- a/src/test/java/org/mockitousage/bugs/varargs/VarargsAndAnyPicksUpExtraInvocationsTest.java +++ b/src/test/java/org/mockitousage/bugs/varargs/VarargsAndAnyPicksUpExtraInvocationsTest.java @@ -26,7 +26,7 @@ public void shouldVerifyCorrectlyWithAny() { table.newRow("abc", "def"); // then - verify(table, times(2)).newRow(anyString(), (String[]) any()); + verify(table, times(2)).newRow(anyString(), any(String[].class)); } @Test @@ -36,7 +36,7 @@ public void shouldVerifyCorrectlyNumberOfInvocationsUsingAnyAndEqualArgument() { table.newRow("x", "def"); // then - verify(table, times(2)).newRow(eq("x"), (String[]) any()); + verify(table, times(2)).newRow(eq("x"), any(String[].class)); } @Test diff --git a/src/test/java/org/mockitousage/bugs/varargs/VarargsNotPlayingWithAnyTest.java b/src/test/java/org/mockitousage/bugs/varargs/VarargsNotPlayingWithAnyTest.java deleted file mode 100644 index 6e11a6a7fd..0000000000 --- a/src/test/java/org/mockitousage/bugs/varargs/VarargsNotPlayingWithAnyTest.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2007 Mockito contributors - * This program is made available under the terms of the MIT License. - */ -package org.mockitousage.bugs.varargs; - -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.anyString; -import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import org.junit.Test; -import org.mockito.Mock; -import org.mockitoutil.TestBase; - -// see issue 62 -public class VarargsNotPlayingWithAnyTest extends TestBase { - - interface VarargMethod { - Object run(String... args); - } - - @Mock VarargMethod mock; - - @Test - public void shouldMatchAny() { - mock.run("a", "b"); - - verify(mock).run(anyString(), anyString()); - verify(mock).run((String) any(), (String) any()); - - verify(mock).run((String[]) any()); - - verify(mock, never()).run(); - verify(mock, never()).run(anyString(), eq("f")); - } - - @Test - public void shouldAllowUsinganyForVarArgs() { - mock.run("a", "b"); - verify(mock).run((String[]) any()); - } - - @Test - public void shouldStubUsingAny() { - when(mock.run((String[]) any())).thenReturn("foo"); - - assertEquals("foo", mock.run("a", "b")); - } -} diff --git a/src/test/java/org/mockitousage/configuration/ClassCacheVersusClassReloadingTest.java b/src/test/java/org/mockitousage/configuration/ClassCacheVersusClassReloadingTest.java index 7d9c95061f..6c08198864 100644 --- a/src/test/java/org/mockitousage/configuration/ClassCacheVersusClassReloadingTest.java +++ b/src/test/java/org/mockitousage/configuration/ClassCacheVersusClassReloadingTest.java @@ -74,7 +74,9 @@ private static SimplePerRealmReloadingClassLoader.ReloadClassPredicate reloadMoc return new SimplePerRealmReloadingClassLoader.ReloadClassPredicate() { public boolean acceptReloadOf(String qualifiedName) { return (!qualifiedName.contains("net.bytebuddy") - && qualifiedName.contains("org.mockito")); + && qualifiedName.contains("org.mockito") + && !qualifiedName.contains( + "org.mockito.internal.creation.bytebuddy.inject")); } }; } diff --git a/src/test/java/org/mockitousage/internal/debugging/LocationFactoryTest.java b/src/test/java/org/mockitousage/internal/debugging/LocationFactoryTest.java new file mode 100644 index 0000000000..072c13fc02 --- /dev/null +++ b/src/test/java/org/mockitousage/internal/debugging/LocationFactoryTest.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockitousage.internal.debugging; + +import org.junit.Test; +import org.mockito.internal.debugging.LocationFactory; +import org.mockitoutil.TestBase; + +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; + +public class LocationFactoryTest extends TestBase { + + @Test + public void shouldLocationNotContainGetStackTraceMethod() { + assertThat(LocationFactory.create().toString()) + .contains("shouldLocationNotContainGetStackTraceMethod"); + } + + @Test + public void provides_location_class() { + // when + final List files = new ArrayList(); + new Runnable() { // anonymous inner class adds stress to the check + public void run() { + files.add(LocationFactory.create().getSourceFile()); + } + }.run(); + + // then + assertEquals("LocationFactoryTest.java", files.get(0)); + } +} diff --git a/src/test/java/org/mockitousage/internal/debugging/LocationImplTest.java b/src/test/java/org/mockitousage/internal/debugging/LocationImplTest.java deleted file mode 100644 index d20bfce02a..0000000000 --- a/src/test/java/org/mockitousage/internal/debugging/LocationImplTest.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2007 Mockito contributors - * This program is made available under the terms of the MIT License. - */ -package org.mockitousage.internal.debugging; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.assertEquals; - -import java.util.ArrayList; -import java.util.List; - -import org.junit.Test; -import org.mockito.internal.debugging.LocationImpl; -import org.mockito.internal.exceptions.stacktrace.StackTraceFilter; -import org.mockitoutil.TestBase; - -@SuppressWarnings("serial") -public class LocationImplTest extends TestBase { - - @Test - public void shouldLocationNotContainGetStackTraceMethod() { - assertThat(new LocationImpl().toString()) - .contains("shouldLocationNotContainGetStackTraceMethod"); - } - - @Test - public void shouldBeSafeInCaseForSomeReasonFilteredStackTraceIsEmpty() { - // given - StackTraceFilter filterReturningEmptyArray = - new StackTraceFilter() { - @Override - public StackTraceElement[] filter(StackTraceElement[] target, boolean keepTop) { - return new StackTraceElement[0]; - } - - @Override - public StackTraceElement filterFirst(Throwable target, boolean isInline) { - return null; - } - }; - - // when - String loc = new LocationImpl(filterReturningEmptyArray).toString(); - - // then - assertEquals("-> at <>", loc); - } - - @Test - public void provides_location_class() { - // when - final List files = new ArrayList(); - new Runnable() { // anonymous inner class adds stress to the check - public void run() { - files.add(new LocationImpl().getSourceFile()); - } - }.run(); - - // then - assertEquals("LocationImplTest.java", files.get(0)); - } -} diff --git a/src/test/java/org/mockitousage/matchers/CapturingArgumentsTest.java b/src/test/java/org/mockitousage/matchers/CapturingArgumentsTest.java index b4b6e1a335..e0146de780 100644 --- a/src/test/java/org/mockitousage/matchers/CapturingArgumentsTest.java +++ b/src/test/java/org/mockitousage/matchers/CapturingArgumentsTest.java @@ -4,16 +4,19 @@ */ package org.mockitousage.matchers; +import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.*; import static org.mockito.Mockito.*; import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import java.util.Set; -import org.assertj.core.api.Assertions; import org.junit.Test; import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.exceptions.base.MockitoException; import org.mockito.exceptions.verification.WantedButNotInvoked; import org.mockitousage.IMethods; @@ -21,7 +24,7 @@ public class CapturingArgumentsTest extends TestBase { - class Person { + private static class Person { private final Integer age; @@ -34,9 +37,9 @@ public int getAge() { } } - class BulkEmailService { + private static class BulkEmailService { - private EmailService service; + private final EmailService service; public BulkEmailService(EmailService service) { this.service = service; @@ -54,11 +57,11 @@ interface EmailService { boolean sendEmailTo(Person person); } - EmailService emailService = mock(EmailService.class); - BulkEmailService bulkEmailService = new BulkEmailService(emailService); - IMethods mock = mock(IMethods.class); + private final EmailService emailService = mock(EmailService.class); + private final BulkEmailService bulkEmailService = new BulkEmailService(emailService); + private final IMethods mock = mock(IMethods.class); + @Captor private ArgumentCaptor> listCaptor; - @SuppressWarnings("deprecation") @Test public void should_allow_assertions_on_captured_argument() { // given @@ -110,7 +113,7 @@ public void should_print_captor_matcher() { fail(); } catch (WantedButNotInvoked e) { // then - assertThat(e).hasMessageContaining(""); + assertThat(e).hasMessageContaining(""); } } @@ -124,7 +127,7 @@ public void should_allow_assertions_on_captured_null() { // then verify(emailService).sendEmailTo(argument.capture()); - assertEquals(null, argument.getValue()); + assertNull(argument.getValue()); } @Test @@ -135,6 +138,7 @@ public void should_allow_construction_of_captor_for_parameterized_type_in_a_conv assertNotNull(argument); } + @SuppressWarnings("unchecked") @Test public void should_allow_construction_of_captor_for_a_more_specific_type() { // the test passes if this expression compiles @@ -166,7 +170,7 @@ public void should_capture_when_stubbing_only_when_entire_invocation_matches() { mock.simpleMethod("bar", 2); // then - Assertions.assertThat(argument.getAllValues()).containsOnly("bar"); + assertThat(argument.getAllValues()).containsOnly("bar"); } @Test @@ -180,7 +184,7 @@ public void should_say_something_smart_when_misused() { } @Test - public void should_capture_when_full_arg_list_matches() throws Exception { + public void should_capture_when_full_arg_list_matches() { // given ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); @@ -208,7 +212,7 @@ public void should_capture_int_by_creating_captor_with_primitive_wrapper() { } @Test - public void should_capture_int_by_creating_captor_with_primitive() throws Exception { + public void should_capture_int_by_creating_captor_with_primitive() { // given ArgumentCaptor argument = ArgumentCaptor.forClass(int.class); @@ -221,66 +225,227 @@ public void should_capture_int_by_creating_captor_with_primitive() throws Except } @Test - public void should_capture_byte_vararg_by_creating_captor_with_primitive() throws Exception { + public void should_not_capture_int_by_creating_captor_with_primitive() { + // given + ArgumentCaptor argument = ArgumentCaptor.forClass(int.class); + + // when + mock.forObject(10L); + + // then + verify(mock, never()).forObject(argument.capture()); + } + + @Test + public void should_capture_byte_vararg_by_creating_captor_with_primitive() { // given ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(byte.class); // when - mock.varargsbyte((byte) 1, (byte) 2); + mock.varargsbyte((byte) 1); // then verify(mock).varargsbyte(argumentCaptor.capture()); + assertEquals((byte) 1, (byte) argumentCaptor.getValue()); + assertThat(argumentCaptor.getAllValues()).containsExactly((byte) 1); + } + + @Test + public void should_capture_byte_vararg_by_creating_captor_with_primitive_2_args() { + // given + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(byte.class); + + // when + mock.varargsbyte((byte) 1, (byte) 2); + + // then + verify(mock).varargsbyte(argumentCaptor.capture(), argumentCaptor.capture()); assertEquals((byte) 2, (byte) argumentCaptor.getValue()); - Assertions.assertThat(argumentCaptor.getAllValues()).containsExactly((byte) 1, (byte) 2); + assertThat(argumentCaptor.getAllValues()).containsExactly((byte) 1, (byte) 2); } @Test - public void should_capture_byte_vararg_by_creating_captor_with_primitive_wrapper() - throws Exception { + public void should_capture_byte_vararg_by_creating_captor_with_primitive_array() { // given - ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Byte.class); + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(byte[].class); // when + mock.varargsbyte(); mock.varargsbyte((byte) 1, (byte) 2); + // then + verify(mock, times(2)).varargsbyte(argumentCaptor.capture()); + assertThat(argumentCaptor.getValue()).containsExactly(new byte[] {1, 2}); + assertThat(argumentCaptor.getAllValues()).containsExactly(new byte[] {}, new byte[] {1, 2}); + } + + @Test + public void should_capture_byte_vararg_by_creating_captor_with_primitive_wrapper() { + // given + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Byte.class); + + // when + mock.varargsbyte((byte) 1); + // then verify(mock).varargsbyte(argumentCaptor.capture()); - assertEquals((byte) 2, (byte) argumentCaptor.getValue()); - Assertions.assertThat(argumentCaptor.getAllValues()).containsExactly((byte) 1, (byte) 2); + assertEquals((byte) 1, (byte) argumentCaptor.getValue()); + assertThat(argumentCaptor.getAllValues()).containsExactly((byte) 1); } @Test - public void should_capture_vararg() throws Exception { + public void should_not_capture_empty_vararg_with_single_captor() { // given ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(String.class); // when - mock.mixedVarargs(42, "a", "b", "c"); + mock.mixedVarargs(42); + + // then + verify(mock, never()).mixedVarargs(any(), argumentCaptor.capture()); + } + + @Test + public void should_capture_single_vararg_with_single_captor() { + // given + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(String.class); + + // when + mock.mixedVarargs(42, "a"); // then verify(mock).mixedVarargs(any(), argumentCaptor.capture()); - Assertions.assertThat(argumentCaptor.getAllValues()).containsExactly("a", "b", "c"); + assertThat(argumentCaptor.getValue()).isEqualTo("a"); + } + + @Test + public void should_not_capture_multiple_vararg_with_single_captor() { + // given + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(String.class); + + // when + mock.mixedVarargs(42, "a", "b"); + + // then + verify(mock, never()).mixedVarargs(any(), argumentCaptor.capture()); } @Test - public void should_capture_all_vararg() throws Exception { + public void should_capture_multiple_vararg_with_multiple_captor() { // given ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(String.class); + // when + mock.mixedVarargs(42, "a", "b"); + + // then + verify(mock).mixedVarargs(any(), argumentCaptor.capture(), argumentCaptor.capture()); + assertThat(argumentCaptor.getValue()).isEqualTo("b"); + assertThat(argumentCaptor.getAllValues()).isEqualTo(asList("a", "b")); + } + + @Test + public void should_not_capture_multiple_vararg_some_null_with_single_captor() { + // given + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(String.class); + + // when + mock.mixedVarargs(42, "a", null); + + // then + verify(mock, never()).mixedVarargs(any(), argumentCaptor.capture()); + } + + @Test + public void should_capture_empty_vararg_with_array_captor() { + // given + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(String[].class); + + // when + mock.mixedVarargs(42); + + // then + verify(mock).mixedVarargs(any(), argumentCaptor.capture()); + assertThat(argumentCaptor.getValue()).isEqualTo(new String[] {}); + assertThat(argumentCaptor.getAllValues()).containsExactly(new String[] {}); + } + + @Test + public void should_capture_single_vararg_with_array_captor() { + // given + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(String[].class); + + // when + mock.mixedVarargs(42, "a"); + + // then + verify(mock).mixedVarargs(any(), argumentCaptor.capture()); + assertThat(argumentCaptor.getValue()).isEqualTo(new String[] {"a"}); + assertThat(argumentCaptor.getAllValues()).containsExactly(new String[] {"a"}); + } + + @Test + public void should_capture_multiple_vararg_with_array_captor() { + // given + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(String[].class); + // when mock.mixedVarargs(42, "a", "b", "c"); - mock.mixedVarargs(42, "again ?!"); + + // then + verify(mock).mixedVarargs(any(), argumentCaptor.capture()); + assertThat(argumentCaptor.getAllValues()).containsExactly(new String[] {"a", "b", "c"}); + } + + @Test + public void should_capture_multiple_vararg_some_null_with_array_captor() { + // given + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(String[].class); + + // when + mock.mixedVarargs(42, "a", null, "c"); + + // then + verify(mock).mixedVarargs(any(), argumentCaptor.capture()); + assertThat(argumentCaptor.getAllValues()).containsExactly(new String[] {"a", null, "c"}); + } + + @Test + public void should_capture_multiple_invocations_with_captor() { + // given + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(String.class); + + // when + mock.mixedVarargs(42, "a", "b"); + mock.mixedVarargs(42, "c", "d"); + + // then + verify(mock, times(2)) + .mixedVarargs(any(), argumentCaptor.capture(), argumentCaptor.capture()); + + assertThat(argumentCaptor.getValue()).isEqualTo("d"); + assertThat(argumentCaptor.getAllValues()).containsExactly("a", "b", "c", "d"); + } + + @Test + public void should_capture_multiple_invocations_with_array_captor() { + // given + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(String[].class); + + // when + mock.mixedVarargs(42, "a", "b"); + mock.mixedVarargs(42, "c", "d"); // then verify(mock, times(2)).mixedVarargs(any(), argumentCaptor.capture()); - Assertions.assertThat(argumentCaptor.getAllValues()) - .containsExactly("a", "b", "c", "again ?!"); + assertThat(argumentCaptor.getValue()).isEqualTo(new String[] {"c", "d"}); + assertThat(argumentCaptor.getAllValues()) + .containsExactly(new String[] {"a", "b"}, new String[] {"c", "d"}); } @Test - public void should_capture_one_arg_even_when_using_vararg_captor_on_nonvararg_method() - throws Exception { + public void should_capture_one_arg_even_when_using_vararg_captor_on_nonvararg_method() { // given ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(String.class); @@ -289,11 +454,11 @@ public void should_capture_one_arg_even_when_using_vararg_captor_on_nonvararg_me // then verify(mock).simpleMethod(argumentCaptor.capture(), eq(2)); - Assertions.assertThat(argumentCaptor.getAllValues()).containsExactly("a"); + assertThat(argumentCaptor.getAllValues()).containsExactly("a"); } @Test - public void captures_correctly_when_captor_used_multiple_times() throws Exception { + public void captures_correctly_when_captor_used_multiple_times() { // given ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(String.class); @@ -308,11 +473,11 @@ public void captures_correctly_when_captor_used_multiple_times() throws Exceptio argumentCaptor.capture(), argumentCaptor.capture(), argumentCaptor.capture()); - Assertions.assertThat(argumentCaptor.getAllValues()).containsExactly("a", "b", "c"); + assertThat(argumentCaptor.getAllValues()).containsExactly("a", "b", "c"); } @Test - public void captures_correctly_when_captor_used_on_pure_vararg_method() throws Exception { + public void captures_correctly_when_captor_used_on_pure_vararg_method() { // given ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(String.class); @@ -321,6 +486,43 @@ public void captures_correctly_when_captor_used_on_pure_vararg_method() throws E // then verify(mock).varargs(eq(42), argumentCaptor.capture()); - Assertions.assertThat(argumentCaptor.getValue()).contains("capturedValue"); + assertThat(argumentCaptor.getValue()).contains("capturedValue"); + } + + @SuppressWarnings("unchecked") + @Test + public void should_capture_by_type() { + // When: + mock.simpleMethod(Set.of()); + mock.simpleMethod(new ArrayList<>(0)); + + // Then: + ArgumentCaptor> captor = ArgumentCaptor.forClass(ArrayList.class); + verify(mock).simpleMethod(captor.capture()); + assertThat(captor.getAllValues()).containsExactly(List.of()); + } + + @Test + public void should_capture_by_type_using_annotation() { + // When: + mock.simpleMethod(Set.of()); + mock.simpleMethod(new ArrayList<>(0)); + + // Then: + verify(mock).simpleMethod(listCaptor.capture()); + assertThat(listCaptor.getAllValues()).containsExactly(List.of()); + } + + @SuppressWarnings("unchecked") + @Test + public void should_always_capture_nulls() { + // When: + mock.simpleMethod((Set) null); + mock.simpleMethod((List) null); + + // Then: + ArgumentCaptor> captor = ArgumentCaptor.forClass(ArrayList.class); + verify(mock, times(2)).simpleMethod(captor.capture()); + assertThat(captor.getAllValues()).containsExactly(null, null); } } diff --git a/src/test/java/org/mockitousage/matchers/HamcrestMatchersTest.java b/src/test/java/org/mockitousage/matchers/HamcrestMatchersTest.java index 3f16618c2f..261a3c3c96 100644 --- a/src/test/java/org/mockitousage/matchers/HamcrestMatchersTest.java +++ b/src/test/java/org/mockitousage/matchers/HamcrestMatchersTest.java @@ -6,7 +6,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.isA; import static org.junit.Assert.*; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.mockito.hamcrest.MockitoHamcrest.*; @@ -54,7 +57,44 @@ public void verifies_with_hamcrest_matcher() { } } - private class IntMatcher extends BaseMatcher { + @Test + public void does_not_verify_vararg_with_no_items() { + mock.varargs(); + + verify(mock, never()).varargs(argThat(isA(String.class))); + } + + @Test + public void verifies_vararg_with_single_item() { + mock.varargs("a"); + + verify(mock).varargs(argThat(isA(String.class))); + } + + @Test + public void does_not_verify_vararg_with_multiple_items() { + mock.varargs("a", "b"); + + verify(mock, never()).varargs(argThat(isA(String.class))); + } + + @Test + public void verify_vararg_with_multiple_item() { + mock.varargs("a", "b"); + + verify(mock).varargs(argThat(isA(String.class)), argThat(isA(String.class))); + } + + @Test + public void verifies_vararg_with_any_num_items() { + mock.varargs(); + mock.varargs("a"); + mock.varargs("a", "b"); + + verify(mock, times(3)).varargs(argThat(isA(String[].class), String[].class)); + } + + private final class IntMatcher extends BaseMatcher { public boolean matches(Object o) { return true; } diff --git a/src/test/java/org/mockitousage/matchers/MatchersTest.java b/src/test/java/org/mockitousage/matchers/MatchersTest.java index 777c49a063..f89b44b731 100644 --- a/src/test/java/org/mockitousage/matchers/MatchersTest.java +++ b/src/test/java/org/mockitousage/matchers/MatchersTest.java @@ -20,6 +20,7 @@ import static org.mockito.AdditionalMatchers.lt; import static org.mockito.AdditionalMatchers.not; import static org.mockito.AdditionalMatchers.or; +import static org.mockito.ArgumentMatchers.assertArg; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.any; @@ -52,6 +53,7 @@ import java.util.RandomAccess; import java.util.regex.Pattern; +import org.junit.ComparisonFailure; import org.junit.Test; import org.mockito.ArgumentMatchers; import org.mockito.Mockito; @@ -624,4 +626,51 @@ public void nullable_matcher() throws Exception { verify(mock, times(2)).oneArg(nullable(Character.class)); } + + @Test + public void assertArg_matcher() throws Exception { + mock.oneArg("hello"); + + verify(mock).oneArg(assertArg((String it) -> assertEquals("hello", it))); + } + + @Test + public void assertArg_matcher_fails_when_assertion_fails() throws Exception { + mock.oneArg("hello"); + + try { + verify(mock).oneArg(assertArg((String it) -> assertEquals("not-hello", it))); + fail("Should throw an exception"); + } catch (ComparisonFailure e) { + // do nothing + } + } + + @Test + public void can_invoke_method_on_mock_after_assert_arg() throws Exception { + mock.oneArg("hello"); + + try { + verify(mock).oneArg(assertArg((String it) -> assertEquals("not-hello", it))); + fail("Should throw an exception"); + } catch (ComparisonFailure e) { + // do nothing + } + + mock.oneArg("hello"); + } + + @Test + public void can_verify_on_mock_after_assert_arg() throws Exception { + mock.oneArg("hello"); + + try { + verify(mock).oneArg(assertArg((String it) -> assertEquals("not-hello", it))); + fail("Should throw an exception"); + } catch (ComparisonFailure e) { + // do nothing + } + + verify(mock).oneArg("hello"); + } } diff --git a/src/test/java/org/mockitousage/matchers/VarargsTest.java b/src/test/java/org/mockitousage/matchers/VarargsTest.java index dfb726942b..5daba370eb 100644 --- a/src/test/java/org/mockitousage/matchers/VarargsTest.java +++ b/src/test/java/org/mockitousage/matchers/VarargsTest.java @@ -4,12 +4,18 @@ */ package org.mockitousage.matchers; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.Assert.fail; +import static org.mockito.AdditionalMatchers.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; import static org.mockito.ArgumentMatchers.isNotNull; import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.ArgumentMatchers.same; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -28,11 +34,13 @@ import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.mockitousage.IMethods; +import org.mockitousage.IMethods.BaseType; public class VarargsTest { @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); @Captor private ArgumentCaptor captor; + @Captor private ArgumentCaptor arrayCaptor; @Mock private IMethods mock; private static final Condition NULL = @@ -52,27 +60,17 @@ public void shouldMatchVarArgs_noArgs() { } @Test - @Ignore("This test must succeed but is fails currently, see github issue #616") public void shouldMatchEmptyVarArgs_noArgsIsNotNull() { mock.varargs(); - verify(mock).varargs(isNotNull()); + verify(mock).varargs(isNotNull(String[].class)); } @Test - @Ignore("This test must succeed but is fails currently, see github issue #616") public void shouldMatchEmptyVarArgs_noArgsIsNull() { - mock.varargs(); - - verify(mock).varargs(isNull()); - } - - @Test - @Ignore("This test must succeed but is fails currently, see github issue #616") - public void shouldMatchEmptyVarArgs_noArgsIsNotNullArray() { - mock.varargs(); + mock.varargs((String[]) null); - verify(mock).varargs((String[]) isNotNull()); + verify(mock).varargs(isNull(String[].class)); } @Test @@ -85,18 +83,18 @@ public void shouldMatchVarArgs_oneNullArg_eqNull() { @Test public void shouldMatchVarArgs_oneNullArg_isNull() { - Object arg = null; - mock.varargs(arg); + mock.varargs((Object) null); - verify(mock).varargs(ArgumentMatchers.isNull()); + verify(mock).varargs(ArgumentMatchers.isNull()); + verify(mock, never()).varargs(isNull(Object[].class)); } @Test public void shouldMatchVarArgs_nullArrayArg() { - Object[] argArray = null; - mock.varargs(argArray); + mock.varargs((Object[]) null); - verify(mock).varargs(ArgumentMatchers.isNull()); + verify(mock).varargs(isNull(Object[].class)); + verify(mock).varargs(ArgumentMatchers.isNull()); } @Test @@ -114,28 +112,28 @@ public void shouldnotMatchVarArgs_twoArgsOneMatcher() { public void shouldMatchVarArgs_emptyVarArgsOneAnyMatcher() { mock.varargs(); - verify(mock).varargs((String[]) any()); // any() -> VarargMatcher + verify(mock).varargs(any(String[].class)); } @Test public void shouldMatchVarArgs_oneArgsOneAnyMatcher() { mock.varargs(1); - verify(mock).varargs(ArgumentMatchers.any()); // any() -> VarargMatcher + verify(mock).varargs(any(Object[].class)); } @Test public void shouldMatchVarArgs_twoArgsOneAnyMatcher() { mock.varargs(1, 2); - verify(mock).varargs(ArgumentMatchers.any()); // any() -> VarargMatcher + verify(mock).varargs(any(Object[].class)); } @Test public void shouldMatchVarArgs_twoArgsTwoAnyMatcher() { mock.varargs(1, 2); - verify(mock).varargs(any(), ArgumentMatchers.any()); // any() -> VarargMatcher + verify(mock).varargs(any(), ArgumentMatchers.any()); } @Test @@ -144,7 +142,7 @@ public void shouldMatchVarArgs_twoArgsThreeAnyMatcher() { assertThatThrownBy( () -> { - verify(mock).varargs(any(), any(), any()); // any() -> VarargMatcher + verify(mock).varargs(any(), any(), any()); }) .hasMessageContaining("Argument(s) are different"); } @@ -178,11 +176,10 @@ public void shouldMatchVarArgs_emptyByteArray() { } @Test - @Ignore public void shouldMatchEmptyVarArgs_emptyArrayIsNotNull() { mock.varargsbyte(); - verify(mock).varargsbyte((byte[]) isNotNull()); + verify(mock).varargsbyte(isNotNull(byte[].class)); } @Test @@ -196,9 +193,9 @@ public void shouldMatchVarArgs_oneArgIsNotNull() { public void shouldCaptureVarArgs_noArgs() { mock.varargs(); - verify(mock).varargs(captor.capture()); + verify(mock).varargs(arrayCaptor.capture()); - assertThat(captor).isEmpty(); + assertThatCaptor(arrayCaptor).contains(new String[] {}); } @Test @@ -208,7 +205,7 @@ public void shouldCaptureVarArgs_oneNullArg_eqNull() { verify(mock).varargs(captor.capture()); - assertThat(captor).areExactly(1, NULL); + assertThatCaptor(captor).areExactly(1, NULL); } /** @@ -221,16 +218,16 @@ public void shouldCaptureVarArgs_nullArrayArg() { mock.varargs(argArray); verify(mock).varargs(captor.capture()); - assertThat(captor).areExactly(1, NULL); + assertThatCaptor(captor).areExactly(1, NULL); } @Test public void shouldCaptureVarArgs_twoArgsOneCapture() { mock.varargs("1", "2"); - verify(mock).varargs(captor.capture()); + verify(mock).varargs(arrayCaptor.capture()); - assertThat(captor).contains("1", "2"); + assertThatCaptor(arrayCaptor).contains(new String[] {"1", "2"}); } @Test @@ -239,16 +236,7 @@ public void shouldCaptureVarArgs_twoArgsTwoCaptures() { verify(mock).varargs(captor.capture(), captor.capture()); - assertThat(captor).contains("1", "2"); - } - - @Test - public void shouldCaptureVarArgs_oneNullArgument() { - mock.varargs("1", null); - - verify(mock).varargs(captor.capture()); - - assertThat(captor).contains("1", (String) null); + assertThatCaptor(captor).contains("1", "2"); } @Test @@ -257,7 +245,7 @@ public void shouldCaptureVarArgs_oneNullArgument2() { verify(mock).varargs(captor.capture(), captor.capture()); - assertThat(captor).contains("1", (String) null); + assertThatCaptor(captor).contains("1", (String) null); } @Test @@ -277,7 +265,7 @@ public void shouldCaptureVarArgs_3argsCaptorMatcherMix() { verify(mock).varargs(captor.capture(), eq("2"), captor.capture()); - assertThat(captor).containsExactly("1", "3"); + assertThatCaptor(captor).containsExactly("1", "3"); } @Test @@ -290,7 +278,7 @@ public void shouldNotCaptureVarArgs_3argsCaptorMatcherMix() { } catch (ArgumentsAreDifferent expected) { } - assertThat(captor).isEmpty(); + assertThatCaptor(captor).isEmpty(); } @Test @@ -304,16 +292,7 @@ public void shouldNotCaptureVarArgs_1args2captures() { .isInstanceOf(ArgumentsAreDifferent.class); } - /** - * As of v2.0.0-beta.118 this test fails. Once the github issues: - *
        - *
      • '#584 ArgumentCaptor can't capture varargs-arrays - *
      • #565 ArgumentCaptor should be type aware' are fixed this test must - * succeed - *
      - */ @Test - @Ignore("Blocked by github issue: #584 & #565") public void shouldCaptureVarArgsAsArray() { mock.varargs("1", "2"); @@ -321,7 +300,7 @@ public void shouldCaptureVarArgsAsArray() { verify(mock).varargs(varargCaptor.capture()); - assertThat(varargCaptor).containsExactly(new String[] {"1", "2"}); + assertThatCaptor(varargCaptor).containsExactly(new String[] {"1", "2"}); } @Test @@ -342,8 +321,267 @@ public void shouldNotMatchVaraArgs() { Assertions.assertThat(mock.varargsObject(1)).isNull(); } - private static AbstractListAssert> assertThat( + @Test + public void shouldDifferentiateNonVarargVariant() { + given(mock.methodWithVarargAndNonVarargVariants(any(String.class))) + .willReturn("single arg method"); + + assertThat(mock.methodWithVarargAndNonVarargVariants("a")).isEqualTo("single arg method"); + assertThat(mock.methodWithVarargAndNonVarargVariants(new String[] {"a"})).isNull(); + assertThat(mock.methodWithVarargAndNonVarargVariants("a", "b")).isNull(); + } + + @Test + public void shouldMockVarargsInvocation_single_vararg_matcher() { + given(mock.methodWithVarargAndNonVarargVariants(any(String[].class))) + .willReturn("var arg method"); + + assertThat(mock.methodWithVarargAndNonVarargVariants("a")).isNull(); + assertThat(mock.methodWithVarargAndNonVarargVariants(new String[] {"a"})) + .isEqualTo("var arg method"); + assertThat(mock.methodWithVarargAndNonVarargVariants("a", "b")).isEqualTo("var arg method"); + } + + @Test + public void shouldMockVarargsInvocation_multiple_vararg_matcher() { + given(mock.methodWithVarargAndNonVarargVariants(any(String.class), any(String.class))) + .willReturn("var arg method"); + + assertThat(mock.methodWithVarargAndNonVarargVariants("a")).isNull(); + assertThat(mock.methodWithVarargAndNonVarargVariants(new String[] {"a"})).isNull(); + assertThat(mock.methodWithVarargAndNonVarargVariants("a", "b")).isEqualTo("var arg method"); + assertThat(mock.methodWithVarargAndNonVarargVariants(new String[] {"a", "b"})) + .isEqualTo("var arg method"); + assertThat(mock.methodWithVarargAndNonVarargVariants("a", "b", "c")).isNull(); + } + + @Test + public void shouldMockVarargsInvocationForSuperType() { + given(mock.varargsReturningString(any(Object[].class))).willReturn("a"); + + assertThat(mock.varargsReturningString("a", "b")).isEqualTo("a"); + } + + @Test + public void shouldHandleArrayVarargsMethods() { + given(mock.arrayVarargsMethod(any(String[][].class))).willReturn(1); + + assertThat(mock.arrayVarargsMethod(new String[] {})).isEqualTo(1); + } + + @Test + public void shouldCaptureVarArgs_NullArrayArg1() { + mock.varargs((String[]) null); + ArgumentCaptor captor = ArgumentCaptor.forClass(String[].class); + + verify(mock).varargs(captor.capture()); + + assertThat(captor.getValue()).isNull(); + } + + @Test + public void shouldCaptureVarArgs_NullArrayArg2() { + mock.varargs((String[]) null); + ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + + verify(mock).varargs(captor.capture()); + + assertThat(captor.getValue()).isNull(); + } + + @Test + public void shouldVerifyVarArgs_any_NullArrayArg1() { + mock.varargs((String[]) null); + + verify(mock).varargs(any()); + } + + @Test + public void shouldVerifyVarArgs_any_NullArrayArg2() { + mock.varargs((String) null); + + verify(mock).varargs(any()); + } + + @Test + public void shouldVerifyVarArgs_eq_NullArrayArg1() { + mock.varargs((String[]) null); + + verify(mock).varargs(eq(null)); + } + + @Test + public void shouldVerifyVarArgs_eq_NullArrayArg2() { + mock.varargs((String) null); + + verify(mock).varargs(eq(null)); + } + + @Test + public void shouldVerifyVarArgs_isNull_NullArrayArg() { + mock.varargs((String) null); + + verify(mock).varargs(isNull(String.class)); + } + + @Test + public void shouldVerifyVarArgs_isNull_NullArrayArg2() { + mock.varargs((String) null); + + verify(mock).varargs(isNull()); + } + + @Test + public void shouldVerifyExactlyOneVarArg_isA() { + mock.varargs("one param"); + + verify(mock).varargs(isA(String.class)); + } + + @Test + public void shouldNotVerifyExactlyOneVarArg_isA() { + mock.varargs("two", "params"); + + verify(mock, never()).varargs(isA(String.class)); + } + + @Test + public void shouldVerifyVarArgArray_isA() { + mock.varargs("one param"); + + verify(mock).varargs(isA(String[].class)); + } + + @Test + public void shouldVerifyVarArgArray_isA2() { + mock.varargs("two", "params"); + + verify(mock).varargs(isA(String[].class)); + } + + @Test + public void shouldVerifyExactlyOneVarArg_any() { + mock.varargs("one param"); + + verify(mock).varargs(any(String.class)); + } + + @Test + @Ignore("Fails due to https://github.com/mockito/mockito/issues/1593") + public void shouldNotVerifyExactlyOneVarArg_any() { + mock.varargs("two", "params"); + + verify(mock, never()).varargs(any(String.class)); + } + + @Test + public void shouldMockVarargInvocation_eq() { + given(mock.varargs(eq("one param"))).willReturn(1); + + assertThat(mock.varargs("one param")).isEqualTo(1); + assertThat(mock.varargs()).isEqualTo(0); + assertThat(mock.varargs("different")).isEqualTo(0); + assertThat(mock.varargs("one param", "another")).isEqualTo(0); + } + + @Test + public void shouldVerifyInvocation_eq() { + mock.varargs("one param"); + + verify(mock).varargs(eq("one param")); + verify(mock, never()).varargs(); + verify(mock, never()).varargs(eq("different")); + verify(mock, never()).varargs(eq("one param"), eq("another")); + } + + @Test + public void shouldMockVarargInvocation_eq_raw() { + given(mock.varargs(eq(new String[] {"one param"}))).willReturn(1); + + assertThat(mock.varargs("one param")).isEqualTo(1); + assertThat(mock.varargs()).isEqualTo(0); + assertThat(mock.varargs("different")).isEqualTo(0); + assertThat(mock.varargs("one param", "another")).isEqualTo(0); + } + + @Test + public void shouldVerifyInvocation_eq_raw() { + mock.varargs("one param"); + + verify(mock).varargs(eq(new String[] {"one param"})); + verify(mock, never()).varargs(eq(new String[] {})); + verify(mock, never()).varargs(eq(new String[] {"different"})); + verify(mock, never()).varargs(eq(new String[] {"one param", "another"})); + } + + @Test + public void shouldVerifyInvocation_not() { + mock.varargs("one param"); + + verify(mock).varargs(not(eq(new String[] {"diff"}))); + verify(mock, never()).varargs(not(eq(new String[] {"one param"}))); + } + + @Test + public void shouldVerifyInvocation_same() { + String[] args = {"two", "params"}; + + mock.varargs(args); + + verify(mock).varargs(same(args)); + verify(mock, never()).varargs(same(new String[] {"two", "params"})); + } + + @Test + public void shouldVerifySubTypes() { + mock.polyVararg(new SubType(), new SubType()); + + verify(mock).polyVararg(eq(new SubType()), eq(new SubType())); + verify(mock).polyVararg(eq(new SubType[] {new SubType(), new SubType()})); + verify(mock).polyVararg(eq(new BaseType[] {new SubType(), new SubType()})); + } + + @Test + public void shouldVerifyInvocation_or() { + mock.polyVararg(new SubType(), new SubType()); + + verify(mock) + .polyVararg( + or( + eq(new BaseType[] {new SubType()}), + eq(new SubType[] {new SubType(), new SubType()}))); + verify(mock) + .polyVararg( + or( + eq(new BaseType[] {new SubType(), new SubType()}), + eq(new SubType[] {new SubType()}))); + } + + @Test + public void shouldVerifyInvocation_and() { + mock.polyVararg(new SubType(), new SubType()); + + verify(mock) + .polyVararg( + and( + eq(new BaseType[] {new SubType(), new SubType()}), + eq(new SubType[] {new SubType(), new SubType()}))); + } + + private static AbstractListAssert> assertThatCaptor( ArgumentCaptor captor) { return Assertions.assertThat(captor.getAllValues()); } + + private static class SubType implements BaseType { + @Override + public boolean equals(final Object obj) { + return obj != null && obj.getClass().equals(getClass()); + } + + @Override + public int hashCode() { + return super.hashCode(); + } + } } diff --git a/src/test/java/org/mockitousage/misuse/InvalidUsageTest.java b/src/test/java/org/mockitousage/misuse/InvalidUsageTest.java index 35c135a62e..e4a8275be9 100644 --- a/src/test/java/org/mockitousage/misuse/InvalidUsageTest.java +++ b/src/test/java/org/mockitousage/misuse/InvalidUsageTest.java @@ -4,7 +4,10 @@ */ package org.mockitousage.misuse; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.not; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verifyNoInteractions; @@ -12,11 +15,14 @@ import static org.mockito.Mockito.when; import org.junit.After; +import org.junit.Assume; import org.junit.Test; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.exceptions.base.MockitoException; import org.mockito.exceptions.misusing.MissingMethodInvocationException; +import org.mockito.internal.configuration.plugins.Plugins; +import org.mockito.plugins.InlineMockMaker; import org.mockitousage.IMethods; import org.mockitoutil.TestBase; @@ -155,6 +161,8 @@ final class FinalClass {} @Test public void shouldNotAllowMockingFinalClassesIfDisabled() { + Assume.assumeThat(Plugins.getMockMaker(), not(instanceOf(InlineMockMaker.class))); + assertThatThrownBy( () -> { mock(FinalClass.class); @@ -166,6 +174,12 @@ public void shouldNotAllowMockingFinalClassesIfDisabled() { " - final class"); } + @Test + public void shouldAllowMockingFinalClassesIfEnabled() { + Assume.assumeThat(Plugins.getMockMaker(), instanceOf(InlineMockMaker.class)); + assertThat(mock(FinalClass.class)).isInstanceOf(FinalClass.class); + } + @SuppressWarnings({"CheckReturnValue", "MockitoUsage"}) @Test public void shouldNotAllowMockingPrimitives() { diff --git a/src/test/java/org/mockitousage/spies/PartialMockingWithSpiesTest.java b/src/test/java/org/mockitousage/spies/PartialMockingWithSpiesTest.java index bf616f8d9f..4efcb2b275 100644 --- a/src/test/java/org/mockitousage/spies/PartialMockingWithSpiesTest.java +++ b/src/test/java/org/mockitousage/spies/PartialMockingWithSpiesTest.java @@ -4,6 +4,8 @@ */ package org.mockitousage.spies; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.not; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static org.mockito.Mockito.doThrow; @@ -13,8 +15,12 @@ import static org.mockitoutil.Conditions.methodsInStackTrace; import org.assertj.core.api.Assertions; +import org.junit.Assume; import org.junit.Before; import org.junit.Test; +import org.mockito.internal.configuration.plugins.Plugins; +import org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker; +import org.mockito.internal.util.reflection.ReflectionMemberAccessor; import org.mockitoutil.TestBase; @SuppressWarnings("unchecked") @@ -104,6 +110,9 @@ public void shouldAllowStubbingWithThrowablesMethodsThatDelegateToOtherMethods() @Test public void shouldStackTraceGetFilteredOnUserExceptions() { + Assume.assumeThat( + Plugins.getMemberAccessor(), not(instanceOf(ReflectionMemberAccessor.class))); + try { // when spy.getNameButDelegateToMethodThatThrows(); @@ -119,6 +128,30 @@ public void shouldStackTraceGetFilteredOnUserExceptions() { } } + @Test + public void shouldStackTraceGetFilteredOnUserExceptionsReflection() { + Assume.assumeThat(Plugins.getMockMaker(), instanceOf(InlineByteBuddyMockMaker.class)); + Assume.assumeThat(Plugins.getMemberAccessor(), instanceOf(ReflectionMemberAccessor.class)); + + try { + // when + spy.getNameButDelegateToMethodThatThrows(); + fail(); + } catch (Throwable t) { + // then + Assertions.assertThat(t) + .has( + methodsInStackTrace( + "throwSomeException", + "invoke0", + "invoke", + "invoke", + "invoke", + "getNameButDelegateToMethodThatThrows", + "shouldStackTraceGetFilteredOnUserExceptionsReflection")); + } + } + // @Test //manual verification public void verifyTheStackTrace() { spy.getNameButDelegateToMethodThatThrows(); diff --git a/src/test/java/org/mockitousage/stubbing/StubbingWithAdditionalAnswersTest.java b/src/test/java/org/mockitousage/stubbing/StubbingWithAdditionalAnswersTest.java index d037cfc0a0..2b96bdf0a9 100644 --- a/src/test/java/org/mockitousage/stubbing/StubbingWithAdditionalAnswersTest.java +++ b/src/test/java/org/mockitousage/stubbing/StubbingWithAdditionalAnswersTest.java @@ -56,7 +56,8 @@ public void can_return_arguments_of_invocation() throws Exception { given(iMethods.objectArgMethod(any())).will(returnsFirstArg()); given(iMethods.threeArgumentMethod(eq(0), any(), anyString())).will(returnsSecondArg()); given(iMethods.threeArgumentMethod(eq(1), any(), anyString())).will(returnsLastArg()); - given(iMethods.mixedVarargsReturningString(eq(1), any())).will(returnsArgAt(2)); + given(iMethods.mixedVarargsReturningString(eq(1), any(String[].class))) + .will(returnsArgAt(2)); assertThat(iMethods.objectArgMethod("first")).isEqualTo("first"); assertThat(iMethods.threeArgumentMethod(0, "second", "whatever")).isEqualTo("second"); @@ -66,8 +67,10 @@ public void can_return_arguments_of_invocation() throws Exception { @Test public void can_return_var_arguments_of_invocation() throws Exception { - given(iMethods.mixedVarargsReturningStringArray(eq(1), any())).will(returnsLastArg()); - given(iMethods.mixedVarargsReturningObjectArray(eq(1), any())).will(returnsArgAt(1)); + given(iMethods.mixedVarargsReturningStringArray(eq(1), any(String[].class))) + .will(returnsLastArg()); + given(iMethods.mixedVarargsReturningObjectArray(eq(1), any(String[].class))) + .will(returnsArgAt(1)); assertThat(iMethods.mixedVarargsReturningStringArray(1, "the", "var", "args")) .containsExactlyInAnyOrder("the", "var", "args"); @@ -77,7 +80,8 @@ public void can_return_var_arguments_of_invocation() throws Exception { @Test public void returns_arg_at_throws_on_out_of_range_var_args() throws Exception { - given(iMethods.mixedVarargsReturningString(eq(1), any())).will(returnsArgAt(3)); + given(iMethods.mixedVarargsReturningString(eq(1), any(String[].class))) + .will(returnsArgAt(3)); assertThatThrownBy(() -> iMethods.mixedVarargsReturningString(1, "a", "b")) .isInstanceOf(MockitoException.class) @@ -111,7 +115,7 @@ public void can_return_after_delay() throws Exception { @Test public void can_return_expanded_arguments_of_invocation() throws Exception { - given(iMethods.varargsObject(eq(1), any())).will(returnsArgAt(3)); + given(iMethods.varargsObject(eq(1), any(Object[].class))).will(returnsArgAt(3)); assertThat(iMethods.varargsObject(1, "bob", "alexander", "alice", "carl")) .isEqualTo("alice"); @@ -389,7 +393,7 @@ public void answer( @Test public void can_return_based_on_strongly_types_one_parameter_var_args_function() throws Exception { - given(iMethods.varargs(any())) + given(iMethods.varargs(any(String[].class))) .will( answer( new Answer1() { @@ -406,7 +410,7 @@ public void will_execute_a_void_based_on_strongly_typed_one_parameter_var_args_f throws Exception { final IMethods target = mock(IMethods.class); - given(iMethods.varargs(any())) + given(iMethods.varargs(any(String[].class))) .will( answerVoid( new VoidAnswer1() { @@ -425,7 +429,7 @@ public void answer(String[] s) { @Test public void can_return_based_on_strongly_typed_two_parameter_var_args_function() throws Exception { - given(iMethods.mixedVarargsReturningString(any(), any())) + given(iMethods.mixedVarargsReturningString(any(), any(String[].class))) .will( answer( new Answer2() { @@ -442,7 +446,7 @@ public void will_execute_a_void_based_on_strongly_typed_two_parameter_var_args_f throws Exception { final IMethods target = mock(IMethods.class); - given(iMethods.mixedVarargsReturningString(any(), any())) + given(iMethods.mixedVarargsReturningString(any(), any(String[].class))) .will( answerVoid( new VoidAnswer2() { @@ -463,7 +467,7 @@ public void can_return_based_on_strongly_typed_three_parameter_var_args_function throws Exception { final IMethods target = mock(IMethods.class); - given(iMethods.threeArgumentVarArgsMethod(anyInt(), any(), any())) + given(iMethods.threeArgumentVarArgsMethod(anyInt(), any(), any(String[].class))) .will( answer( new Answer3() { @@ -486,7 +490,7 @@ public void will_execute_a_void_based_on_strongly_typed_three_parameter_var_args throws Exception { final IMethods target = mock(IMethods.class); - given(iMethods.threeArgumentVarArgsMethod(anyInt(), any(), any())) + given(iMethods.threeArgumentVarArgsMethod(anyInt(), any(), any(String[].class))) .will( answerVoid( new VoidAnswer3() { @@ -506,7 +510,7 @@ public void answer(Integer i, String s1, String[] s2) { public void can_return_based_on_strongly_typed_four_parameter_var_args_function() throws Exception { final IMethods target = mock(IMethods.class); - given(iMethods.fourArgumentVarArgsMethod(anyInt(), any(), anyInt(), any())) + given(iMethods.fourArgumentVarArgsMethod(anyInt(), any(), anyInt(), any(String[].class))) .will( answer( new Answer4() { @@ -531,7 +535,7 @@ public void will_execute_a_void_based_on_strongly_typed_four_parameter_var_args_ throws Exception { final IMethods target = mock(IMethods.class); - given(iMethods.fourArgumentVarArgsMethod(anyInt(), any(), anyInt(), any())) + given(iMethods.fourArgumentVarArgsMethod(anyInt(), any(), anyInt(), any(String[].class))) .will( answerVoid( new VoidAnswer4() { @@ -552,7 +556,9 @@ public void answer( public void can_return_based_on_strongly_typed_five_parameter_var_args_function() throws Exception { final IMethods target = mock(IMethods.class); - given(iMethods.fiveArgumentVarArgsMethod(anyInt(), any(), anyInt(), any(), any())) + given( + iMethods.fiveArgumentVarArgsMethod( + anyInt(), any(), anyInt(), any(), any(String[].class))) .will( answer( new Answer5() { @@ -580,7 +586,9 @@ public void will_execute_a_void_based_on_strongly_typed_five_parameter_var_args_ throws Exception { final IMethods target = mock(IMethods.class); - given(iMethods.fiveArgumentVarArgsMethod(anyInt(), any(), anyInt(), any(), any())) + given( + iMethods.fiveArgumentVarArgsMethod( + anyInt(), any(), anyInt(), any(), any(String[].class))) .will( answerVoid( new VoidAnswer5() { @@ -605,7 +613,9 @@ public void answer( public void can_return_based_on_strongly_typed_six_parameter_var_args_function() throws Exception { final IMethods target = mock(IMethods.class); - given(iMethods.sixArgumentVarArgsMethod(anyInt(), any(), anyInt(), any(), any(), any())) + given( + iMethods.sixArgumentVarArgsMethod( + anyInt(), any(), anyInt(), any(), any(), any(String[].class))) .will( answer( new Answer6< @@ -641,7 +651,9 @@ public String answer( public void will_execute_a_void_returning_strongly_typed_six_parameter_var_args_function() throws Exception { final IMethods target = mock(IMethods.class); - given(iMethods.sixArgumentVarArgsMethod(anyInt(), any(), anyInt(), any(), any(), any())) + given( + iMethods.sixArgumentVarArgsMethod( + anyInt(), any(), anyInt(), any(), any(), any(String[].class))) .will( answerVoid( new VoidAnswer6< @@ -667,7 +679,7 @@ public void answer( @Test public void can_accept_array_supertype_for_strongly_typed_var_args_function() throws Exception { - given(iMethods.varargs(any())) + given(iMethods.varargs(any(String[].class))) .will( answer( new Answer1() { @@ -681,7 +693,7 @@ public Integer answer(Object[] s) { @Test public void can_accept_non_vararg_answer_on_var_args_function() throws Exception { - given(iMethods.varargs(any())) + given(iMethods.varargs(any(String[].class))) .will( answer( new Answer2() { @@ -695,7 +707,7 @@ public Integer answer(String s1, String s2) { @Test public void should_work_with_var_args_with_no_elements() throws Exception { - given(iMethods.varargs(any())) + given(iMethods.varargs(any(String[].class))) .will( answer( new Answer1() { @@ -709,7 +721,7 @@ public Integer answer(String[] s) { @Test public void should_work_with_array_var_args() throws Exception { - given(iMethods.arrayVarargsMethod(any())) + given(iMethods.arrayVarargsMethod(any(String[][].class))) .will( answer( new Answer1() { diff --git a/src/test/java/org/mockitousage/stubbing/StubbingWithCustomAnswerTest.java b/src/test/java/org/mockitousage/stubbing/StubbingWithCustomAnswerTest.java index 5591bc47b5..dada3d5710 100644 --- a/src/test/java/org/mockitousage/stubbing/StubbingWithCustomAnswerTest.java +++ b/src/test/java/org/mockitousage/stubbing/StubbingWithCustomAnswerTest.java @@ -7,7 +7,6 @@ import static org.junit.Assert.*; import static org.mockito.Mockito.*; -import java.lang.reflect.Method; import java.util.Set; import org.junit.Test; @@ -110,7 +109,8 @@ public void shouldMakeSureTheInterfaceDoesNotChange() throws Exception { new Answer() { public String answer(InvocationOnMock invocation) throws Throwable { assertTrue(invocation.getArguments().getClass().isArray()); - assertEquals(Method.class, invocation.getMethod().getClass()); + assertEquals( + IMethods.class, invocation.getMethod().getDeclaringClass()); return "assertions passed"; } diff --git a/src/test/java/org/mockitousage/verification/DescriptiveMessagesWhenVerificationFailsTest.java b/src/test/java/org/mockitousage/verification/DescriptiveMessagesWhenVerificationFailsTest.java index bae7c31df7..a1a29c1aad 100644 --- a/src/test/java/org/mockitousage/verification/DescriptiveMessagesWhenVerificationFailsTest.java +++ b/src/test/java/org/mockitousage/verification/DescriptiveMessagesWhenVerificationFailsTest.java @@ -83,7 +83,7 @@ public void should_print_actual_and_wanted_in_line() { String actual = "\n" - + "Actual invocations have different arguments:" + + "Actual invocations have different arguments at position [1]:" + "\n" + "iMethods.varargs(1, 2);"; @@ -370,37 +370,43 @@ public void should_never_break_method_string_when_no_args_in_method() throws Exc @Test public void should_print_fully_qualified_name_when_arguments_classes_have_same_simple_name() { + Date dateArg = new Date(0); + String stringifiedDateArg = dateArg.toString(); + try { - mock.overloadedMethodWithSameClassNameArguments(new Date(0), new IMethods.Date(0)); - verify(mock) - .overloadedMethodWithSameClassNameArguments(new IMethods.Date(0), new Date(0)); + mock.overloadedMethodWithSameClassNameArguments(dateArg, new IMethods.Date(0)); + verify(mock).overloadedMethodWithSameClassNameArguments(new IMethods.Date(0), dateArg); fail(); } catch (Throwable e) { String wanted = - "\n" - + "Argument(s) are different! Wanted:" - + "\n" - + "iMethods.overloadedMethodWithSameClassNameArguments(" - + "\n" - + " (org.mockitousage.IMethods.Date) 0," - + "\n" - + " (java.sql.Date) 1970-01-01" - + "\n" - + ");"; + String.format( + "\n" + + "Argument(s) are different! Wanted:" + + "\n" + + "iMethods.overloadedMethodWithSameClassNameArguments(" + + "\n" + + " (org.mockitousage.IMethods.Date) 0," + + "\n" + + " (java.sql.Date) %s" + + "\n" + + ");", + stringifiedDateArg); assertThat(e).hasMessageContaining(wanted); String actual = - "\n" - + "Actual invocations have different arguments:" - + "\n" - + "iMethods.overloadedMethodWithSameClassNameArguments(" - + "\n" - + " (java.sql.Date) 1970-01-01," - + "\n" - + " (org.mockitousage.IMethods.Date) 0" - + "\n" - + ");"; + String.format( + "\n" + + "Actual invocations have different arguments at positions [0, 1]:" + + "\n" + + "iMethods.overloadedMethodWithSameClassNameArguments(" + + "\n" + + " (java.sql.Date) %s," + + "\n" + + " (org.mockitousage.IMethods.Date) 0" + + "\n" + + ");", + stringifiedDateArg); assertThat(e).hasMessageContaining(actual); } } @@ -429,7 +435,7 @@ public void should_print_fully_qualified_name_when_arguments_classes_have_same_s String actual = "\n" - + "Actual invocations have different arguments:" + + "Actual invocations have different arguments at positions [0, 1]:" + "\n" + "iMethods.overloadedMethodWithDifferentClassNameArguments(" + "\n" @@ -445,43 +451,50 @@ public void should_print_fully_qualified_name_when_arguments_classes_have_same_s @Test public void should_print_fully_qualified_name_when_some_arguments_classes_have_same_simple_name() { + Date dateArg = new Date(0); + String stringifiedDateArg = dateArg.toString(); + try { mock.overloadedMethodWithSameClassNameArguments( - new Date(0), "string", new IMethods.Date(0)); + dateArg, "string", new IMethods.Date(0)); verify(mock) .overloadedMethodWithSameClassNameArguments( - new IMethods.Date(0), "string", new Date(0)); + new IMethods.Date(0), "string", dateArg); fail(); } catch (Throwable e) { String wanted = - "\n" - + "Argument(s) are different! Wanted:" - + "\n" - + "iMethods.overloadedMethodWithSameClassNameArguments(" - + "\n" - + " (org.mockitousage.IMethods.Date) 0," - + "\n" - + " \"string\"," - + "\n" - + " (java.sql.Date) 1970-01-01" - + "\n" - + ");"; + String.format( + "\n" + + "Argument(s) are different! Wanted:" + + "\n" + + "iMethods.overloadedMethodWithSameClassNameArguments(" + + "\n" + + " (org.mockitousage.IMethods.Date) 0," + + "\n" + + " \"string\"," + + "\n" + + " (java.sql.Date) %s" + + "\n" + + ");", + stringifiedDateArg); assertThat(e).hasMessageContaining(wanted); String actual = - "\n" - + "Actual invocations have different arguments:" - + "\n" - + "iMethods.overloadedMethodWithSameClassNameArguments(" - + "\n" - + " (java.sql.Date) 1970-01-01," - + "\n" - + " \"string\"," - + "\n" - + " (org.mockitousage.IMethods.Date) 0" - + "\n" - + ");"; + String.format( + "\n" + + "Actual invocations have different arguments at positions [0, 2]:" + + "\n" + + "iMethods.overloadedMethodWithSameClassNameArguments(" + + "\n" + + " (java.sql.Date) %s," + + "\n" + + " \"string\"," + + "\n" + + " (org.mockitousage.IMethods.Date) 0" + + "\n" + + ");", + stringifiedDateArg); assertThat(e).hasMessageContaining(actual); } } diff --git a/src/test/java/org/mockitoutil/TestBase.java b/src/test/java/org/mockitoutil/TestBase.java index 2fe89504e4..3f772d020b 100644 --- a/src/test/java/org/mockitoutil/TestBase.java +++ b/src/test/java/org/mockitoutil/TestBase.java @@ -17,7 +17,7 @@ import org.mockito.StateMaster; import org.mockito.internal.MockitoCore; import org.mockito.internal.configuration.ConfigurationAccess; -import org.mockito.internal.debugging.LocationImpl; +import org.mockito.internal.debugging.LocationFactory; import org.mockito.internal.invocation.InterceptedInvocation; import org.mockito.internal.invocation.InvocationBuilder; import org.mockito.internal.invocation.InvocationMatcher; @@ -84,7 +84,7 @@ protected static Invocation invocationOf(Class type, String methodName, Objec new SerializableMethod(type.getMethod(methodName, types)), args, InterceptedInvocation.NO_OP, - new LocationImpl(), + LocationFactory.create(), 1); } diff --git a/subprojects/android/src/main/resources/mockito-extensions/org.mockito.plugins.MemberAccessor b/subprojects/android/src/main/resources/mockito-extensions/org.mockito.plugins.MemberAccessor new file mode 100644 index 0000000000..71111e3378 --- /dev/null +++ b/subprojects/android/src/main/resources/mockito-extensions/org.mockito.plugins.MemberAccessor @@ -0,0 +1 @@ +member-accessor-reflection diff --git a/subprojects/androidTest/androidTest.gradle b/subprojects/androidTest/androidTest.gradle index b6c32495be..072c222639 100644 --- a/subprojects/androidTest/androidTest.gradle +++ b/subprojects/androidTest/androidTest.gradle @@ -6,31 +6,30 @@ plugins { } android { - compileSdkVersion 31 - buildToolsVersion "30.0.3" + namespace = "org.mockitousage.androidtest" + compileSdk = 33 defaultConfig { - applicationId "org.mockitousage.androidtest" - minSdkVersion 21 - targetSdkVersion 31 - versionCode 1 - versionName "1.0" + minSdk = 26 + targetSdk = 33 + versionCode = 1 + versionName = "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 } kotlinOptions { - jvmTarget = '1.8' + jvmTarget = '11' + } +} + +androidComponents { + beforeVariants(selector().withBuildType("release")) { + enable = false } } @@ -38,16 +37,13 @@ apply from: "$rootDir/gradle/dependencies.gradle" dependencies { implementation libraries.kotlin.stdlib - implementation libraries.android.ktx - implementation libraries.android.compat - implementation libraries.android.material - testImplementation project(":inline") + testImplementation project(":") testImplementation libraries.junit4 testImplementation libraries.junitJupiterApi testImplementation libraries.junitJupiterEngine + androidTestImplementation libraries.android.runner androidTestImplementation libraries.android.junit - androidTestImplementation libraries.android.espresso androidTestImplementation project(":android") } diff --git a/subprojects/androidTest/proguard-rules.pro b/subprojects/androidTest/proguard-rules.pro deleted file mode 100644 index f1b424510d..0000000000 --- a/subprojects/androidTest/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile diff --git a/subprojects/androidTest/src/androidTest/java/org/mockitousage/androidtest/BasicInstrumentedTests.kt b/subprojects/androidTest/src/androidTest/java/org/mockitousage/androidtest/BasicInstrumentedTests.kt index 2fa04c8bd6..07378171fc 100644 --- a/subprojects/androidTest/src/androidTest/java/org/mockitousage/androidtest/BasicInstrumentedTests.kt +++ b/subprojects/androidTest/src/androidTest/java/org/mockitousage/androidtest/BasicInstrumentedTests.kt @@ -35,7 +35,6 @@ class BasicInstrumentedTests { fun mockAndUseBasicClassUsingAnnotatedMock() { val basicClass = BasicOpenClassReceiver(mockedViaAnnotationBasicOpenClass) basicClass.callDependencyMethod() - throw Exception("java") } @Test diff --git a/subprojects/androidTest/src/main/AndroidManifest.xml b/subprojects/androidTest/src/main/AndroidManifest.xml index cc2901a874..955375b4e3 100644 --- a/subprojects/androidTest/src/main/AndroidManifest.xml +++ b/subprojects/androidTest/src/main/AndroidManifest.xml @@ -1,13 +1,10 @@ - + + android:supportsRtl="true" /> diff --git a/subprojects/androidTest/src/main/res/drawable-v24/ic_launcher_foreground.xml b/subprojects/androidTest/src/main/res/drawable-v24/ic_launcher_foreground.xml deleted file mode 100644 index 7706ab9e6d..0000000000 --- a/subprojects/androidTest/src/main/res/drawable-v24/ic_launcher_foreground.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - diff --git a/subprojects/androidTest/src/main/res/drawable/ic_launcher_background.xml b/subprojects/androidTest/src/main/res/drawable/ic_launcher_background.xml deleted file mode 100644 index 07d5da9cbf..0000000000 --- a/subprojects/androidTest/src/main/res/drawable/ic_launcher_background.xml +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/subprojects/androidTest/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/subprojects/androidTest/src/main/res/mipmap-anydpi-v26/ic_launcher.xml deleted file mode 100644 index 6b78462d61..0000000000 --- a/subprojects/androidTest/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/subprojects/androidTest/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/subprojects/androidTest/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml deleted file mode 100644 index 6b78462d61..0000000000 --- a/subprojects/androidTest/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/subprojects/androidTest/src/main/res/mipmap-hdpi/ic_launcher.png b/subprojects/androidTest/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index a571e60098..0000000000 Binary files a/subprojects/androidTest/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/subprojects/androidTest/src/main/res/mipmap-hdpi/ic_launcher_round.png b/subprojects/androidTest/src/main/res/mipmap-hdpi/ic_launcher_round.png deleted file mode 100644 index 61da551c55..0000000000 Binary files a/subprojects/androidTest/src/main/res/mipmap-hdpi/ic_launcher_round.png and /dev/null differ diff --git a/subprojects/androidTest/src/main/res/mipmap-mdpi/ic_launcher.png b/subprojects/androidTest/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index c41dd28531..0000000000 Binary files a/subprojects/androidTest/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/subprojects/androidTest/src/main/res/mipmap-mdpi/ic_launcher_round.png b/subprojects/androidTest/src/main/res/mipmap-mdpi/ic_launcher_round.png deleted file mode 100644 index db5080a752..0000000000 Binary files a/subprojects/androidTest/src/main/res/mipmap-mdpi/ic_launcher_round.png and /dev/null differ diff --git a/subprojects/androidTest/src/main/res/mipmap-xhdpi/ic_launcher.png b/subprojects/androidTest/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 6dba46dab1..0000000000 Binary files a/subprojects/androidTest/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/subprojects/androidTest/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/subprojects/androidTest/src/main/res/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index da31a871c8..0000000000 Binary files a/subprojects/androidTest/src/main/res/mipmap-xhdpi/ic_launcher_round.png and /dev/null differ diff --git a/subprojects/androidTest/src/main/res/mipmap-xxhdpi/ic_launcher.png b/subprojects/androidTest/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 15ac681720..0000000000 Binary files a/subprojects/androidTest/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/subprojects/androidTest/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/subprojects/androidTest/src/main/res/mipmap-xxhdpi/ic_launcher_round.png deleted file mode 100644 index b216f2d313..0000000000 Binary files a/subprojects/androidTest/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/subprojects/androidTest/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/subprojects/androidTest/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index f25a419744..0000000000 Binary files a/subprojects/androidTest/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/subprojects/androidTest/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/subprojects/androidTest/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png deleted file mode 100644 index e96783ccce..0000000000 Binary files a/subprojects/androidTest/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/subprojects/androidTest/src/main/res/values-night/themes.xml b/subprojects/androidTest/src/main/res/values-night/themes.xml deleted file mode 100644 index 70198506d0..0000000000 --- a/subprojects/androidTest/src/main/res/values-night/themes.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - diff --git a/subprojects/androidTest/src/main/res/values/colors.xml b/subprojects/androidTest/src/main/res/values/colors.xml deleted file mode 100644 index ca1931bca9..0000000000 --- a/subprojects/androidTest/src/main/res/values/colors.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - #FFBB86FC - #FF6200EE - #FF3700B3 - #FF03DAC5 - #FF018786 - #FF000000 - #FFFFFFFF - diff --git a/subprojects/androidTest/src/main/res/values/strings.xml b/subprojects/androidTest/src/main/res/values/strings.xml index 9aab1293ac..33d20491a8 100644 --- a/subprojects/androidTest/src/main/res/values/strings.xml +++ b/subprojects/androidTest/src/main/res/values/strings.xml @@ -1,3 +1,9 @@ + + + Mockito Android Tests diff --git a/subprojects/androidTest/src/main/res/values/themes.xml b/subprojects/androidTest/src/main/res/values/themes.xml deleted file mode 100644 index d21572ebb6..0000000000 --- a/subprojects/androidTest/src/main/res/values/themes.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - diff --git a/subprojects/errorprone/errorprone.gradle b/subprojects/errorprone/errorprone.gradle index e3ef7a7b77..effbb91d2c 100644 --- a/subprojects/errorprone/errorprone.gradle +++ b/subprojects/errorprone/errorprone.gradle @@ -10,14 +10,30 @@ dependencies { implementation project.rootProject implementation libraries.errorprone - testImplementation 'junit:junit:4.13-beta-1' + testImplementation 'junit:junit:4.13.2' testImplementation libraries.errorproneTestApi } test { inputs.files(configurations.errorproneJavac).withNormalizer(ClasspathNormalizer) - jvmArgs += "-Xbootclasspath/p:${configurations.errorproneJavac.asPath}" + jvmArgs += "-Xbootclasspath/a:${configurations.errorproneJavac.asPath}" + jvmArgs += "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED" + jvmArgs += "--add-exports=jdk.compiler/com.sun.tools.javac.type=ALL-UNNAMED" + jvmArgs += "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED" + jvmArgs += "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED" + jvmArgs += "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED" + jvmArgs += "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED" + jvmArgs += "--add-exports=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED" + jvmArgs += "--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED" + jvmArgs += "--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED" +} + +tasks.withType(JavaCompile) { + options.compilerArgs << "--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED" + options.compilerArgs << "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED" + options.compilerArgs << "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED" +} - // ErrorProne can only run on JDK 8 - it.enabled = !JavaVersion.current().isJava9Compatible() +tasks.withType(Javadoc) { + options.addBooleanOption("-add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", true) } diff --git a/subprojects/extTest/src/test/java/org/mockitousage/plugins/switcher/MyPluginSwitch.java b/subprojects/extTest/src/test/java/org/mockitousage/plugins/switcher/MyPluginSwitch.java index eb4423f361..cf60ee63ec 100644 --- a/subprojects/extTest/src/test/java/org/mockitousage/plugins/switcher/MyPluginSwitch.java +++ b/subprojects/extTest/src/test/java/org/mockitousage/plugins/switcher/MyPluginSwitch.java @@ -14,6 +14,10 @@ public class MyPluginSwitch implements PluginSwitch { static List invokedFor = new LinkedList(); public boolean isEnabled(String pluginClassName) { + if (!pluginClassName.startsWith("org.mockitousage")) { + return false; + } + invokedFor.add(pluginClassName); return true; } diff --git a/subprojects/groovyInlineTest/groovyInlineTest.gradle b/subprojects/groovyInlineTest/groovyInlineTest.gradle index 1618ac6009..16e95a9118 100644 --- a/subprojects/groovyInlineTest/groovyInlineTest.gradle +++ b/subprojects/groovyInlineTest/groovyInlineTest.gradle @@ -5,7 +5,7 @@ description = "Integration test for using mockito-inline with Groovy." apply from: "$rootDir/gradle/dependencies.gradle" dependencies { - testImplementation project(":inline") + testImplementation project(":") testImplementation libraries.groovy testImplementation libraries.junit4 } diff --git a/subprojects/inline/inline.gradle b/subprojects/inline/inline.gradle deleted file mode 100644 index f96cffce9f..0000000000 --- a/subprojects/inline/inline.gradle +++ /dev/null @@ -1,21 +0,0 @@ -description = "Mockito preconfigured inline mock maker (intermediate and to be superseeded by automatic usage in a future version)" - -apply from: "$rootDir/gradle/java-library.gradle" - -dependencies { - api project.rootProject - testImplementation libraries.junit4 - testImplementation libraries.assertj -} - -tasks.javadoc.enabled = false - -//required by the "StressTest.java" and "OneLinerStubStressTest.java" -test.maxHeapSize = "256m" -retryTest.maxHeapSize = "256m" - -if (JavaVersion.current().java9Compatible) { - test { - jvmArgs '--illegal-access=deny' - } -} diff --git a/subprojects/inline/src/main/resources/mockito-extensions/org.mockito.plugins.MockMaker b/subprojects/inline/src/main/resources/mockito-extensions/org.mockito.plugins.MockMaker deleted file mode 100644 index ca6ee9cea8..0000000000 --- a/subprojects/inline/src/main/resources/mockito-extensions/org.mockito.plugins.MockMaker +++ /dev/null @@ -1 +0,0 @@ -mock-maker-inline \ No newline at end of file diff --git a/subprojects/inlineTest/inlineTest.gradle b/subprojects/inlineTest/inlineTest.gradle new file mode 100644 index 0000000000..4cc6726c5a --- /dev/null +++ b/subprojects/inlineTest/inlineTest.gradle @@ -0,0 +1,24 @@ +plugins { + id 'java' +} + +description = "Mockito preconfigured inline mock maker (intermediate and to be superseeded by automatic usage in a future version)" + +apply from: "$rootDir/gradle/dependencies.gradle" + +sourceCompatibility = 11 +targetCompatibility = 11 + +dependencies { + implementation project.rootProject + testImplementation libraries.junit4 + testImplementation libraries.assertj +} + +tasks.javadoc.enabled = false + +test { + jvmArgs '--illegal-access=deny' + //required by the "StressTest.java" and "OneLinerStubStressTest.java" + maxHeapSize '256m' +} diff --git a/subprojects/inline/src/test/java/org/mockitoinline/ConstructionMockRuleTest.java b/subprojects/inlineTest/src/test/java/org/mockitoinline/ConstructionMockRuleTest.java similarity index 100% rename from subprojects/inline/src/test/java/org/mockitoinline/ConstructionMockRuleTest.java rename to subprojects/inlineTest/src/test/java/org/mockitoinline/ConstructionMockRuleTest.java diff --git a/subprojects/inline/src/test/java/org/mockitoinline/ConstructionMockTest.java b/subprojects/inlineTest/src/test/java/org/mockitoinline/ConstructionMockTest.java similarity index 90% rename from subprojects/inline/src/test/java/org/mockitoinline/ConstructionMockTest.java rename to subprojects/inlineTest/src/test/java/org/mockitoinline/ConstructionMockTest.java index 7eb87c5702..adbe3fe3ee 100644 --- a/subprojects/inline/src/test/java/org/mockitoinline/ConstructionMockTest.java +++ b/subprojects/inlineTest/src/test/java/org/mockitoinline/ConstructionMockTest.java @@ -11,11 +11,13 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.mockito.Mockito.withSettings; import java.util.Collections; import java.util.concurrent.atomic.AtomicReference; import org.junit.Test; +import org.mockito.MockMakers; import org.mockito.MockedConstruction; import org.mockito.Mockito; import org.mockito.exceptions.base.MockitoException; @@ -154,6 +156,21 @@ public void testConstructionMockMustNotTargetAbstractClass() { .hasMessageContaining("It is not possible to construct primitive types or abstract types"); } + @Test + public void testConstructionMocksMustNotUseCustomMockMaker() { + assertThatThrownBy( + () -> { + try (MockedConstruction ignored = Mockito.mockConstruction( + Dummy.class, + withSettings().mockMaker(MockMakers.INLINE)) + ) { + new Dummy(); + } + }) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("you cannot override the MockMaker for construction mocks"); + } + static class Dummy { diff --git a/subprojects/inline/src/test/java/org/mockitoinline/EnumMockingTest.java b/subprojects/inlineTest/src/test/java/org/mockitoinline/EnumMockingTest.java similarity index 100% rename from subprojects/inline/src/test/java/org/mockitoinline/EnumMockingTest.java rename to subprojects/inlineTest/src/test/java/org/mockitoinline/EnumMockingTest.java diff --git a/subprojects/inline/src/test/java/org/mockitoinline/FinalClassMockingTest.java b/subprojects/inlineTest/src/test/java/org/mockitoinline/FinalClassMockingTest.java similarity index 100% rename from subprojects/inline/src/test/java/org/mockitoinline/FinalClassMockingTest.java rename to subprojects/inlineTest/src/test/java/org/mockitoinline/FinalClassMockingTest.java diff --git a/subprojects/inline/src/test/java/org/mockitoinline/HierarchyPreInitializationTest.java b/subprojects/inlineTest/src/test/java/org/mockitoinline/HierarchyPreInitializationTest.java similarity index 100% rename from subprojects/inline/src/test/java/org/mockitoinline/HierarchyPreInitializationTest.java rename to subprojects/inlineTest/src/test/java/org/mockitoinline/HierarchyPreInitializationTest.java diff --git a/subprojects/inline/src/test/java/org/mockitoinline/InOrderVerificationTest.java b/subprojects/inlineTest/src/test/java/org/mockitoinline/InOrderVerificationTest.java similarity index 100% rename from subprojects/inline/src/test/java/org/mockitoinline/InOrderVerificationTest.java rename to subprojects/inlineTest/src/test/java/org/mockitoinline/InOrderVerificationTest.java diff --git a/subprojects/inline/src/test/java/org/mockitoinline/InitializationTest.java b/subprojects/inlineTest/src/test/java/org/mockitoinline/InitializationTest.java similarity index 100% rename from subprojects/inline/src/test/java/org/mockitoinline/InitializationTest.java rename to subprojects/inlineTest/src/test/java/org/mockitoinline/InitializationTest.java diff --git a/subprojects/inline/src/test/java/org/mockitoinline/OneLinerStubStressTest.java b/subprojects/inlineTest/src/test/java/org/mockitoinline/OneLinerStubStressTest.java similarity index 100% rename from subprojects/inline/src/test/java/org/mockitoinline/OneLinerStubStressTest.java rename to subprojects/inlineTest/src/test/java/org/mockitoinline/OneLinerStubStressTest.java diff --git a/subprojects/inline/src/test/java/org/mockitoinline/PluginTest.java b/subprojects/inlineTest/src/test/java/org/mockitoinline/PluginTest.java similarity index 100% rename from subprojects/inline/src/test/java/org/mockitoinline/PluginTest.java rename to subprojects/inlineTest/src/test/java/org/mockitoinline/PluginTest.java diff --git a/subprojects/inline/src/test/java/org/mockitoinline/RecursionTest.java b/subprojects/inlineTest/src/test/java/org/mockitoinline/RecursionTest.java similarity index 100% rename from subprojects/inline/src/test/java/org/mockitoinline/RecursionTest.java rename to subprojects/inlineTest/src/test/java/org/mockitoinline/RecursionTest.java diff --git a/subprojects/inline/src/test/java/org/mockitoinline/SpyWithConstructorTest.java b/subprojects/inlineTest/src/test/java/org/mockitoinline/SpyWithConstructorTest.java similarity index 100% rename from subprojects/inline/src/test/java/org/mockitoinline/SpyWithConstructorTest.java rename to subprojects/inlineTest/src/test/java/org/mockitoinline/SpyWithConstructorTest.java diff --git a/subprojects/inline/src/test/java/org/mockitoinline/StaticMockRuleTest.java b/subprojects/inlineTest/src/test/java/org/mockitoinline/StaticMockRuleTest.java similarity index 100% rename from subprojects/inline/src/test/java/org/mockitoinline/StaticMockRuleTest.java rename to subprojects/inlineTest/src/test/java/org/mockitoinline/StaticMockRuleTest.java diff --git a/subprojects/inline/src/test/java/org/mockitoinline/StaticMockTest.java b/subprojects/inlineTest/src/test/java/org/mockitoinline/StaticMockTest.java similarity index 93% rename from subprojects/inline/src/test/java/org/mockitoinline/StaticMockTest.java rename to subprojects/inlineTest/src/test/java/org/mockitoinline/StaticMockTest.java index eaa3ed1970..c9f7a58cce 100644 --- a/subprojects/inline/src/test/java/org/mockitoinline/StaticMockTest.java +++ b/subprojects/inlineTest/src/test/java/org/mockitoinline/StaticMockTest.java @@ -212,6 +212,15 @@ public void testStaticMockMustUseValidMatchers() { } } + @Test + public void testStaticMockVarargs() { + assertEquals("foobar", Dummy.fooVarargs("foo", "bar")); + try (MockedStatic ignored = Mockito.mockStatic(Dummy.class)) { + assertNull(Dummy.fooVarargs("foo", "bar")); + } + assertEquals("foobar", Dummy.fooVarargs("foo", "bar")); + } + static class Dummy { static String var1 = null; @@ -227,5 +236,13 @@ static void fooVoid(String var2) { static void fooVoid(String var2, String var3) { var1 = var2; } + + static String fooVarargs(String... args) { + StringBuilder sb = new StringBuilder(); + for (String arg : args) { + sb.append(arg); + } + return sb.toString(); + } } } diff --git a/subprojects/inline/src/test/java/org/mockitoinline/StaticRuleTest.java b/subprojects/inlineTest/src/test/java/org/mockitoinline/StaticRuleTest.java similarity index 100% rename from subprojects/inline/src/test/java/org/mockitoinline/StaticRuleTest.java rename to subprojects/inlineTest/src/test/java/org/mockitoinline/StaticRuleTest.java diff --git a/subprojects/inline/src/test/java/org/mockitoinline/StaticRunnerTest.java b/subprojects/inlineTest/src/test/java/org/mockitoinline/StaticRunnerTest.java similarity index 100% rename from subprojects/inline/src/test/java/org/mockitoinline/StaticRunnerTest.java rename to subprojects/inlineTest/src/test/java/org/mockitoinline/StaticRunnerTest.java diff --git a/subprojects/inline/src/test/java/org/mockitoinline/StressTest.java b/subprojects/inlineTest/src/test/java/org/mockitoinline/StressTest.java similarity index 100% rename from subprojects/inline/src/test/java/org/mockitoinline/StressTest.java rename to subprojects/inlineTest/src/test/java/org/mockitoinline/StressTest.java diff --git a/subprojects/inline/src/test/java/org/mockitoinline/StubbingLocationTest.java b/subprojects/inlineTest/src/test/java/org/mockitoinline/StubbingLocationTest.java similarity index 100% rename from subprojects/inline/src/test/java/org/mockitoinline/StubbingLocationTest.java rename to subprojects/inlineTest/src/test/java/org/mockitoinline/StubbingLocationTest.java diff --git a/subprojects/inline/src/test/java/org/mockitoinline/SubconstructorMockTest.java b/subprojects/inlineTest/src/test/java/org/mockitoinline/SubconstructorMockTest.java similarity index 100% rename from subprojects/inline/src/test/java/org/mockitoinline/SubconstructorMockTest.java rename to subprojects/inlineTest/src/test/java/org/mockitoinline/SubconstructorMockTest.java diff --git a/subprojects/inline/src/test/java/org/mockitoinline/SuperCallTest.java b/subprojects/inlineTest/src/test/java/org/mockitoinline/SuperCallTest.java similarity index 100% rename from subprojects/inline/src/test/java/org/mockitoinline/SuperCallTest.java rename to subprojects/inlineTest/src/test/java/org/mockitoinline/SuperCallTest.java diff --git a/subprojects/inline/src/test/java/org/mockitoinline/bugs/CyclicMockMethodArgumentMemoryLeakTest.java b/subprojects/inlineTest/src/test/java/org/mockitoinline/bugs/CyclicMockMethodArgumentMemoryLeakTest.java similarity index 100% rename from subprojects/inline/src/test/java/org/mockitoinline/bugs/CyclicMockMethodArgumentMemoryLeakTest.java rename to subprojects/inlineTest/src/test/java/org/mockitoinline/bugs/CyclicMockMethodArgumentMemoryLeakTest.java diff --git a/subprojects/inline/src/test/java/org/mockitoinline/bugs/OngoingStubShiftTest.java b/subprojects/inlineTest/src/test/java/org/mockitoinline/bugs/OngoingStubShiftTest.java similarity index 100% rename from subprojects/inline/src/test/java/org/mockitoinline/bugs/OngoingStubShiftTest.java rename to subprojects/inlineTest/src/test/java/org/mockitoinline/bugs/OngoingStubShiftTest.java diff --git a/subprojects/inline/src/test/java/org/mockitoinline/bugs/SelfSpyReferenceMemoryLeakTest.java b/subprojects/inlineTest/src/test/java/org/mockitoinline/bugs/SelfSpyReferenceMemoryLeakTest.java similarity index 100% rename from subprojects/inline/src/test/java/org/mockitoinline/bugs/SelfSpyReferenceMemoryLeakTest.java rename to subprojects/inlineTest/src/test/java/org/mockitoinline/bugs/SelfSpyReferenceMemoryLeakTest.java diff --git a/subprojects/junit-jupiter/junit-jupiter.gradle b/subprojects/junit-jupiter/junit-jupiter.gradle index 99f63e75d5..cbd25d34ab 100644 --- a/subprojects/junit-jupiter/junit-jupiter.gradle +++ b/subprojects/junit-jupiter/junit-jupiter.gradle @@ -25,7 +25,7 @@ jar { 'Bundle-SymbolicName': 'org.mockito.junit-jupiter', 'Bundle-Version': "\${version_cleanup;${project.version}}", '-versionpolicy': '[${version;==;${@}},${version;+;${@}})', - 'Export-Package': "org.mockito.junit.jupiter.*;version=${version}", + 'Export-Package': "org.mockito.junit.jupiter.*;version=${archiveVersion.get()}", '-removeheaders': 'Private-Package', 'Automatic-Module-Name': 'org.mockito.junit.jupiter', '-noextraheaders': 'true', diff --git a/subprojects/junit-jupiter/src/test/java/org/mockitousage/GenericTypeMockMultipleMatchesTest.java b/subprojects/junit-jupiter/src/test/java/org/mockitousage/GenericTypeMockMultipleMatchesTest.java new file mode 100644 index 0000000000..323008eb63 --- /dev/null +++ b/subprojects/junit-jupiter/src/test/java/org/mockitousage/GenericTypeMockMultipleMatchesTest.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2023 Mockito contributors + * This program is made available under the terms of the MIT License. + */ + +package org.mockitousage; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.List; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.exceptions.base.MockitoException; +import org.mockito.junit.jupiter.MockitoExtension; + +/** + * Verify that a {@link MockitoException} is thrown when there are multiple {@link Mock} fields that + * do match a candidate field by type, but cannot be matched by name. + * + * Uses a JUnit 5 extension to obtain the JUnit 5 {@link ExtensionContext} and + * pass it to {@link MockitoExtension#beforeEach(ExtensionContext)}, as the exception + * is thrown during {@link org.junit.jupiter.api.BeforeEach}. + */ +@ExtendWith(GenericTypeMockMultipleMatchesTest.ContextProvidingExtension.class) +public class GenericTypeMockMultipleMatchesTest { + + private static ExtensionContext currentExtensionContext; + + public static class ContextProvidingExtension implements BeforeEachCallback { + @Override + public void beforeEach(ExtensionContext context) throws Exception { + currentExtensionContext = context; + } + } + + private void startMocking(Object testInstance) { + MockitoExtension mockitoExtension = new MockitoExtension(); + mockitoExtension.beforeEach(currentExtensionContext); + } + + @Nested + public class MultipleCandidatesByTypeTest { + public class UnderTestWithMultipleCandidatesByType { + List stringList; + } + + @Mock + List stringList1; + + @Mock + List stringList2; + + @InjectMocks + UnderTestWithMultipleCandidatesByType underTestWithMultipleCandidates = new UnderTestWithMultipleCandidatesByType(); + + @Test + void testMultipleCandidatesByTypes() { + assertThrows(MockitoException.class, () -> startMocking(this)); + } + } + + +} diff --git a/subprojects/junit-jupiter/src/test/java/org/mockitousage/GenericTypeMockTest.java b/subprojects/junit-jupiter/src/test/java/org/mockitousage/GenericTypeMockTest.java new file mode 100644 index 0000000000..4decb2e2e2 --- /dev/null +++ b/subprojects/junit-jupiter/src/test/java/org/mockitousage/GenericTypeMockTest.java @@ -0,0 +1,349 @@ +/* + * Copyright (c) 2023 Mockito contributors + * This program is made available under the terms of the MIT License. + */ + +package org.mockitousage; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.MockitoAnnotations.*; + +import java.sql.Time; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +/** + * Tests that verify Mockito can discern mocks by generic types, so if there are multiple mock candidates + * with the same generic type but different type parameters available for injection into a given field, + * Mockito won't fail to inject (even if mock field name doesn't match under test's field name). + */ +@ExtendWith(MockitoExtension.class) +public class GenericTypeMockTest { + + @Nested + public class SingleTypeParamTest { + public class UnderTestWithSingleTypeParam { + List stringList; + List intList; + } + + @Mock + private List stringListMock; + + @Mock + private List intListMock; + + // must construct non-static inner class ourselves here + // (making it public static classes doesn't work either) + @InjectMocks + private UnderTestWithSingleTypeParam underTestWithSingleTypeParam = new UnderTestWithSingleTypeParam(); + + @Test + void testSingleTypeParam() { + // testing for not null first before testing for equals, + // because assertEquals(null, null) == true, + // so test would succeed if both @Mock and @InjectMocks + // don't work at all + assertNotNull(stringListMock); + assertNotNull(intListMock); + + assertEquals(stringListMock, underTestWithSingleTypeParam.stringList); + assertEquals(intListMock, underTestWithSingleTypeParam.intList); + } + } + + @Nested + public class WildcardTest { + class UnderTestWithWildcard { + Set dateSet; + Set numberSet; + } + + @Mock + Set