An open API service indexing awesome lists of open source software.

https://github.com/joke/spock-mockable

Mock the un-mockable
https://github.com/joke/spock-mockable

mocking spock-extension spock-framework

Last synced: 5 months ago
JSON representation

Mock the un-mockable

Awesome Lists containing this project

README

          

= `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 <>.

* Changes method visibility `private` to `protected`
* Removes `final` from classes and methods
* Supports mocking `static` methods
* Automatically attaches java agent
* Re-defines classes via https://bytebuddy.net/[Byte Buddy] transformation.
* Working with Spock Framework 2.0, 2.1, 2.2 and 2.3

== 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[]

.build.gradle
[source,groovy]
----
dependencies {
testImplementation 'io.github.joke:spock-mockable:x.y.z'
}

// to load the agent even earlier add it as a JVM argument
tasks.withType(Test) {
jvmArgs += ["-javaagent:${classpath.find { it.name.contains('spock-mockable') }.absolutePath}"]
}
----

=== Maven

.pom.xml
[source,xml]
----


io.github.joke
spock-mockable
x.y.z
test

----

== Usage

Under normal circumstances classes needing to undergo transformation are detected automatically.

=== Mocks & Stubs

.Mock definition
[source,groovy]
----
class MySpec extends Specification {
// either
Person person = Mock() // detected class based on type of variable
// or
def person = Mock(Person) // detected class based on Mock parameter
}
----

.Stub definition
[source,groovy]
----
class MySpec extends Specification {
// either
Person person = Stub() // detected class based on type of variable
// or
def person = Stub(Person) // detected class based on Mock parameter
}
----

=== Spies

.Spy definition
[source,groovy]
----
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`].

.Mock static methods
[source,groovy]
----
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.

.Mockable annotation
[source,groovy]
----
@Mockable(className = 'some.package.MyFirstClass')
@Mockable(className = 'some.package.MySecondClass')
@Mockable(packageName = 'some.package')
class PersonSpec extends Specification {
}
----

== 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`.