= spock-mockable
:icons: font
image:https://github.com/joke/spock-mockable/workflows/build/badge.svg?branch=main[] image:https://badgen.net/github/license/joke/spock-mockable[] image:https://badgen.net/github/release/joke/spock-mockable/stable[] image:https://badgen.net/github/dependabot/joke/spock-mockable[] image:https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg[link=https://conventionalcommits.org] image:https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit[pre-commit, link=https://github.com/pre-commit/pre-commit]
spock-mockable
allows creation of mocks otherwise un-mockable by the http://spockframework.org/[Spock Framework].
Spock is not capable of mocking private
or final
classes or methods
because they are not accessible via inheritance. spock-mockable
uses JVM instrumentation to
modify these classes upon loading and apply looser access restrictions.
In consequence Spock is capable of mocking these classes. Refer to <
private
to protected
final
from classes and methodsstatic
methods== Setup
Add the artifact as an additional dependency to your spock setup.
TIP: Even though not strictly necessary it is recommended to register the dependency as a java agent with the JVM. This way more classes can be transformed.
=== Gradle
image:https://badgen.net/github/release/joke/spock-mockable/stable[]
dependencies { testImplementation 'io.github.joke:spock-mockable:x.y.z' }
=== Maven
== Usage
Under normal circumstances classes needing to undergo transformation are detected automatically.
=== Mocks & Stubs
=== Spies
class MySpec extends Specification { // either def person = new Person() Person personSpy = Spy(person) // detected class based on type of variable
// or
Person person = new Person()
def personSpy = Spy(personInstance) // detected class based on Mock parameter
// WARNING!
def person = new Person()
def person2 = personInstance // person2 is dynamic typed and ...
def personSpy = Spy(person2) // ... class type information is lost in this case!
=== Static methods
Static methods can be mocked in a similar fashion like https://spockframework.org/spock/docs/2.3/interaction_based_testing.html#_mocking_static_methods[mocking static methods using GroovySpy
]. All classes which have undergone transformation are
already prepared for mocking there static methods. The original method is called by default similar to a spy.
If an interaction has been registered within a feature method the real method is skipped and the interaction is executed.
A call of the method can also be forced by using
https://spockframework.org/spock/docs/2.3/interaction_based_testing.html#Spies[{ callRealMethod*() }
].
NOTE: Specifications defining interactions with static mocks are automatically annotated with https://spockframework.org/spock/docs/2.3/parallel_execution.html#_isolated_execution[`@Isolated`].
class UtilitySpec extends Specification { def 'mock static method'() { setup: Spy(UtilityClass) // needed if class has not been mocked/spied/stubbed prior
when:
def res = UtilityClass.someStaticMethod('hello')
then:
1 * UtilityClass.someStaticMethod('hello') >> 'mock value'
expect:
res == 'mock value'
}
More examples can be found in link:examples[].
=== Transform special cases
In special cases you might want to manually specify additional classes or packages to undergo transformation. This need might arise if the exact class type can not be referenced in the specification. In this case you can specify arbitrary class or package names manually.
== How does it work
During groovy's compilation phase each specification is analyzed and mock invocations are detected. At the start of a test JVM these detected classes are transformed by the JVM instrumentation regardless of the actual specification there the mock invocation has been detected. This might lead to unexpected behaviour between different specifications.
For the earliest possible transformation of classes start the agent by using the JVM argument (-javaagent
).
IMPORTANT: For agent instrumentation to work the JVM must support this feature. A JRE is most likely not sufficient.
IMPORTANT: The agent is attached to the JVM as early as possible but some classes not be transformed nevertheless because they are used prior. This restriction applies to some java.lang
classes but also to some junit classes.
=== Conditionally disable transformation
You can disable spock-mockable
by setting the JVM system property spock-mockable.disabled=true
.