From 906d01661ffbf0933d8102beb391533bbaee12fd Mon Sep 17 00:00:00 2001 From: Andreas Turban Date: Sun, 3 Mar 2024 19:55:13 +0100 Subject: [PATCH] Static mocking documentation updates (#1907) Moved static mocking into own section. Added sample to do stubbing of all static methods. --- docs/extensions.adoc | 2 +- docs/interaction_based_testing.adoc | 142 ++++++++++-------- .../docs/interaction/StaticSpyDocSpec.groovy | 28 +++- 3 files changed, 105 insertions(+), 67 deletions(-) diff --git a/docs/extensions.adoc b/docs/extensions.adoc index 0957cea2a0..63f475225e 100644 --- a/docs/extensions.adoc +++ b/docs/extensions.adoc @@ -1220,7 +1220,7 @@ include::{sourcedir}/interaction/MockMakerDocSpec.groovy[tag=mock-serializable] The `mockito` mock maker uses `org.mockito.MockMakers.INLINE` under the hood, please see the Mockito manual "Mocking final types, enums and final methods" for all pros and cons, when using `org.mockito.MockMakers.INLINE`. -It also supports mocking of static methods of classes and interfaces with `Mock/Stub/SpyStatic`. +It also supports mocking of static methods of classes and interfaces with `SpyStatic`. See <> section for more details. ==== Custom Mock Maker diff --git a/docs/interaction_based_testing.adoc b/docs/interaction_based_testing.adoc index 0d514fd259..aac9fdb122 100644 --- a/docs/interaction_based_testing.adoc +++ b/docs/interaction_based_testing.adoc @@ -903,6 +903,79 @@ then: 1 * persister.persist("msg") ---- +[[MockingStaticMethods]] +== Mocking Static Methods + +Spock supports two ways of mocking static methods: + +* `SpyStatic()` static mocks: Works with Java and Groovy, but requires a mock maker supporting this, e.g. <> Mock Maker. +* <<#global-groovy-mock-static-methods,Global Groovy mocks>>: Work only for Groovy code not for Java. + +NOTE: Think twice before using this feature. +It might be better to change the design of the code under specification. + +[[static-mocks]] +=== Static Mocks + +You can create static mocks with `SpyStatic()`. + +`SpyStatic()` mocks the static methods of the given type; by default, delegates all calls to the real static methods. +Supports both stubbing and mocking. + +We are using the class `StaticClass` in the examples: + +.Example static class used in the test examples +[source,groovy,indent=0] +---- +include::{sourcedir}/interaction/StaticSpyDocSpec.groovy[tag=mock-static-class] +---- + +We want to mock the method `staticMethod()`, so we can create a static mock for the `StaticClass` type: + +.Mock static method of a class with SpyStatic() +[source,groovy,indent=0] +---- +include::{sourcedir}/interaction/StaticSpyDocSpec.groovy[tag=mock-static1] +---- + +You can also specify the answers after construction by defining the interactions as for a normal mock: + +.Mock static method of a class with interactions +[source,groovy,indent=0] +---- +include::{sourcedir}/interaction/StaticSpyDocSpec.groovy[tag=mock-static-interactions] +---- + +It is also possible to stub all static methods of a class like a `Stub` would do: + +.Stub all static methods of a class with default interaction +[source,groovy,indent=0] +---- +include::{sourcedir}/interaction/StaticSpyDocSpec.groovy[tag=stub-all-static-methods] +---- + +NOTE: The static mocks require a mock maker supporting static methods, e.g. <>. +See <> table for mock makers supporting it. + +=== Static Mocks and Threading + +The static mocks are thread-local, so they do not interfere with concurrent test execution. +But this also means that a static mock will not be active, if your code under test will use other threads. + +A static mock is activated on the thread, which created the mock, up until the feature execution ends. +You can activate all static mocks on a different thread by hand: + +.Use static mocks in a different Thread +[source,groovy,indent=0] +---- +include::{sourcedir}/interaction/StaticSpyDocSpec.groovy[tag=mock-static-different-thread] +---- + +The `spock.mock.MockingApi` provides methods to activate a static mock on other threads: + +* `runWithThreadAwareMocks(Runnable)` +* `withActiveThreadAwareMocks(Callable)` + [[GroovyMocks]] == Groovy Mocks @@ -1027,72 +1100,11 @@ new RealSubscriber("Fred") >> new RealSubscriber("Barney") Now, whenever some code tries to construct a subscriber named Fred, we'll construct a subscriber named Barney instead. -[MockingStaticMethods] -=== Mocking Static Methods - -Spock supports two ways of mocking static methods: - -* `SpyStatic()` static mocks: Works with Java and Groovy, but requires a mock maker supporting this, e.g. <> Mock Maker. -* Global Groovy mocks: Work only for Groovy code not for Java. If parallel execution is enabled, then the test must be annotated with `@ResourceLock(Resources.META_CLASS_REGISTRY)` or `@Isolated`. - -NOTE: Think twice before using this feature. - It might be better to change the design of the code under specification. - -[[static-mocks]] -==== Static Mocks - -You can create static mocks with `SpyStatic()`. -The semantics are the same as for the non-static variants: - -* `SpyStatic()`: Mocks static methods of the given type; by default, delegates all calls to the real static methods. - Supports both stubbing and mocking. - -We are using the class `StaticClass` in the examples: - -.Example static class used in the test examples -[source,groovy,indent=0] ----- -include::{sourcedir}/interaction/StaticSpyDocSpec.groovy[tag=mock-static-class] ----- - -We want to mock the method `staticMethod()`, so we can create a static mock for the `StaticClass` type: - -.Mock static method of a class with SpyStatic() -[source,groovy,indent=0] ----- -include::{sourcedir}/interaction/StaticSpyDocSpec.groovy[tag=mock-static1] ----- - -You can also specify the answers after construction by defining the interactions as for a normal mock: - -.Mock static method of a class with interactions -[source,groovy,indent=0] ----- -include::{sourcedir}/interaction/StaticSpyDocSpec.groovy[tag=mock-static-interactions] ----- - -NOTE: The static mocks require a mock maker supporting static methods, e.g. <>. -See <> table for mock makers supporting it. - -===== Static Mocks and Threading - -The static mocks are thread-local, so they do not interfere with concurrent test execution. -But this also means that a static mock will not be active, if your code under test will use other threads. - -A static mock is activated on the thread, which created the mock, up until the feature execution ends. -You can activate all static mocks on a different thread by hand: - -.Use static mocks in a different Thread -[source,groovy,indent=0] ----- -include::{sourcedir}/interaction/StaticSpyDocSpec.groovy[tag=mock-static-different-thread] ----- - -The interface `spock.mock.IStaticMock` provides API to activate a static mock on other threads. - -==== Global Groovy Mocks for Static Methods +[[global-groovy-mock-static-methods]] +=== Global Groovy Mocks for Static Methods -Global mocks support mocking and stubbing of static methods: +Global mocks support mocking and stubbing of static methods in Groovy code. +If you want to mock static methods for Java code please use <<#static-mocks,Static Mocks>>. [source,groovy] ---- diff --git a/spock-specs/src/test/groovy/org/spockframework/docs/interaction/StaticSpyDocSpec.groovy b/spock-specs/src/test/groovy/org/spockframework/docs/interaction/StaticSpyDocSpec.groovy index 0d81ea949f..a5d8ef885c 100644 --- a/spock-specs/src/test/groovy/org/spockframework/docs/interaction/StaticSpyDocSpec.groovy +++ b/spock-specs/src/test/groovy/org/spockframework/docs/interaction/StaticSpyDocSpec.groovy @@ -16,7 +16,7 @@ package org.spockframework.docs.interaction - +import org.spockframework.mock.ZeroOrNullResponse import spock.lang.Specification import java.util.concurrent.Executors @@ -51,6 +51,28 @@ class StaticSpyDocSpec extends Specification { // end::mock-static-interactions[] } + def "SpyStatic mock all static methods"() { + given: + SpyStatic(StaticClass) + StaticClass._ >> { ZeroOrNullResponse.INSTANCE.respond(delegate) } + + expect: + StaticClass.staticMethod() == null + StaticClass.otherStaticMethod() == null + } + + def "SpyStatic stub all static methods"() { + // tag::stub-all-static-methods[] + given: + SpyStatic(StaticClass) + StaticClass._ >> _ + + expect: + StaticClass.staticMethod() == "" + StaticClass.otherStaticMethod() == "" + // end::stub-all-static-methods[] + } + def "SpyStatic in a different thread"() { // tag::mock-static-different-thread[] given: @@ -87,6 +109,10 @@ class StaticSpyDocSpec extends Specification { static String staticMethod() { return "RealValue" } + + static String otherStaticMethod() { + return "OtherValue" + } } // end::mock-static-class[] }