spock-mockable
allows creation of mocks otherwise un-mockable by the 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 How does it work.
-
Changes method visibility
private
toprotected
-
Removes
final
from classes and methods -
Supports mocking
static
methods -
Automatically attaches java agent
-
Re-defines classes via Byte Buddy transformation.
-
Working with Spock Framework 2.0, 2.1, 2.2 and 2.3
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. |
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}"]
}
Under normal circumstances classes needing to undergo transformation are detected automatically.
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
}
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
}
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 can be mocked in a similar fashion like 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
{ callRealMethod*() }
.
Note
|
Specifications defining interactions with static mocks are automatically annotated with
@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 examples.
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(className = 'some.package.MyFirstClass')
@Mockable(className = 'some.package.MySecondClass')
@Mockable(packageName = 'some.package')
class PersonSpec extends Specification {
}
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.
|