Skip to content

Commit

Permalink
feat!: automatic detected of mockable classes
Browse files Browse the repository at this point in the history
* detect mockables classes automatically

* major code cleanup

BREAKING CHANGE: `@Mockable` changed in favor of automatic detection.

Signed-off-by: Joke de Buhr <joke@xckk.de>
  • Loading branch information
joke committed Nov 5, 2022
1 parent 733bf5f commit fb03556
Show file tree
Hide file tree
Showing 45 changed files with 1,255 additions and 439 deletions.
54 changes: 46 additions & 8 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ image:https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-comm
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 class loading and apply looser access restrictions.
In consequence Spock then capable of mocking these classes.
In consequence Spock is capable of mocking these classes.

* Changes methode visibility `private` to `protected`
* Changes method visibility `private` to `protected`
* Removes `final` from classes and methods
* Automatically attaches java agent
* Redefines classes via https://bytebuddy.net/[Byte Buddy] transformation.
Expand Down Expand Up @@ -51,20 +51,58 @@ dependencies {

== Usage

Add the `@Mockable` annotation to your spock specification.
Under normal circumstances classes needing to undergo transformation are detected automatically.

.Make Person mockable
.Mock definition
[source,groovy]
----
@Mockable(Person)
class PersonSpec extends Specification {
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
}
----

.Make complete package `io.github.joke` mockable
.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!
}
----

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(packages = "io.github.joke")
@Mockable(className = 'some.package.MyFirstClass')
@Mockable(className = 'some.package.MySecondClass')
@Mockable(packageName = 'some.package')
class PersonSpec extends Specification {
}
----
Expand Down
24 changes: 24 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
plugins {
id 'com.github.johnrengelman.shadow' version '7.1.2' apply false
id 'idea'
id 'net.ltgt.errorprone' version '2.0.2' apply false
id 'org.shipkit.shipkit-auto-version' version '1.2.2'
id 'io.freefair.lombok' version '6.5.1'
}

allprojects { project ->
Expand Down Expand Up @@ -34,6 +37,27 @@ allprojects { project ->
}
}

pluginManager.withPlugin('lombok') {
lombok {
version = '1.18.24'
}
}

pluginManager.withPlugin('net.ltgt.errorprone') {
tasks.withType(JavaCompile) {
options.errorprone {
disableWarningsInGeneratedCode = true
error('NullAway')
option('NullAway:AnnotatedPackages', project.group)
}
}
}

tasks.withType(AbstractArchiveTask) {
preserveFileTimestamps = false
reproducibleFileOrder = true
}

pluginManager.withPlugin('maven-publish') {
project.plugins.apply 'signing'

Expand Down
26 changes: 26 additions & 0 deletions dependencies/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
plugins {
id 'java-platform'
}

javaPlatform {
allowDependencies()
}

dependencies {

api platform('org.apache.groovy:groovy-bom:4.0.6')
api platform('org.spockframework:spock-bom:2.3-groovy-4.0')

constraints {
api 'com.google.auto.service:auto-service-annotations:1.0.1'
api 'com.google.auto.service:auto-service:1.0.1'
api 'com.google.dagger:dagger-compiler:2.44'
api 'com.google.dagger:dagger:2.44'
api 'com.google.errorprone:error_prone_core:2.13.1'
api 'com.uber.nullaway:nullaway:0.9.6'
api 'javax.inject:javax.inject:1'
api 'org.jetbrains:annotations:23.0.0'
api 'org.projectlombok:lombok:1.18.24'
}

}
6 changes: 3 additions & 3 deletions examples/kotlin/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ plugins {
}

dependencies {
testImplementation platform('org.apache.groovy:groovy-bom:4.0.6')
testImplementation platform('org.spockframework:spock-bom:2.3-groovy-4.0')
testImplementation enforcedPlatform('org.apache.groovy:groovy-bom:4.0.6')
testImplementation enforcedPlatform('org.spockframework:spock-bom:2.3-groovy-4.0')

testImplementation project(path: ':spock-mockable', configuration: 'shadow')
testImplementation 'org.spockframework:spock-core'
testImplementation project(':spock-mockable')
testImplementation 'org.slf4j:slf4j-simple:2.0.3'
testImplementation 'org.objenesis:objenesis:3.3'
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package io.github.joke.spockmockable.tests

import io.github.joke.spockmockable.Mockable
import org.spockframework.mock.MockUtil
import spock.lang.Specification

@Mockable(Company)
class CompanyTest extends Specification {
def mockUtil = new MockUtil()

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package io.github.joke.spockmockable.tests

import org.spockframework.mock.MockUtil
import spock.lang.Specification

class MockableClassTest extends Specification {

def mockUtil = new MockUtil()

def 'final from class is removed'() {
setup:
Person person = Mock()

expect:
mockUtil.isMock person
}

def 'final from subclass is removed'() {
setup:
Address address = Mock()

expect:
mockUtil.isMock address
}

def 'final from method is removed'() {
setup:
Person person = Mock()

when:
def res = person.firstName

then:
1 * person.firstName >> 'Dorothy'

expect:
res == 'Dorothy'
}

def 'private on method is now protected'() {
setup:
Person person = Mock()

when:
def res = person.lastName

then:
1 * person.lastName >> 'Gale'

expect:
res == 'Gale'
}

}
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package io.github.joke.spockmockable.tests

import io.github.joke.spockmockable.Mockable
import org.spockframework.mock.MockUtil
import spock.lang.Specification

@Mockable([Person, Address])
class PersonTest extends Specification {

def mockUtil = new MockUtil()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import io.github.joke.spockmockable.Mockable
import org.spockframework.mock.MockUtil
import spock.lang.Specification

@Mockable(packages = 'io.github.joke.spockmockable.tests')
@Mockable(packageName = 'io.github.joke.spockmockable.tests')
class PersonTestWithMockablePackage extends Specification {

def mockUtil = new MockUtil()
Expand Down
76 changes: 41 additions & 35 deletions examples/spock/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,79 +20,79 @@ def spock2x = [
dependencies {
testImplementation enforcedPlatform('org.apache.groovy:groovy-bom:4.0.6')
testImplementation enforcedPlatform('org.spockframework:spock-bom:2.2-groovy-4.0')
testImplementation project(':spock-mockable')
testImplementation project(path: ':spock-mockable', configuration: 'shadow')
testImplementation 'org.slf4j:slf4j-simple:2.0.3'
testImplementation 'org.spockframework:spock-core'

spock20groovy25Implementation enforcedPlatform('org.codehaus.groovy:groovy-bom:2.5.18')
spock20groovy25Implementation enforcedPlatform('org.spockframework:spock-bom:2.0-groovy-2.5')
spock20groovy25Implementation project(':spock-mockable')
spock20groovy25Implementation 'org.spockframework:spock-core'
spock20groovy25Implementation 'org.slf4j:slf4j-simple:2.0.2'
spock20groovy25Implementation project(path: ':spock-mockable', configuration: 'shadow')
spock20groovy25Implementation 'org.objenesis:objenesis:3.3'
spock20groovy25Implementation 'org.slf4j:slf4j-simple:2.0.2'
spock20groovy25Implementation 'org.spockframework:spock-core'

spock20groovy30Implementation enforcedPlatform('org.codehaus.groovy:groovy-bom:3.0.9')
spock20groovy30Implementation enforcedPlatform('org.codehaus.groovy:groovy-bom:3.0.13')
spock20groovy30Implementation enforcedPlatform('org.spockframework:spock-bom:2.0-groovy-3.0')
spock20groovy30Implementation project(':spock-mockable')
spock20groovy30Implementation 'org.spockframework:spock-core'
spock20groovy30Implementation 'org.slf4j:slf4j-simple:2.0.2'
spock20groovy30Implementation project(path: ':spock-mockable', configuration: 'shadow')
spock20groovy30Implementation 'org.objenesis:objenesis:3.3'
spock20groovy30Implementation 'org.slf4j:slf4j-simple:2.0.2'
spock20groovy30Implementation 'org.spockframework:spock-core'

spock21groovy25Implementation enforcedPlatform('org.codehaus.groovy:groovy-bom:2.5.18')
spock21groovy25Implementation enforcedPlatform('org.spockframework:spock-bom:2.1-groovy-2.5')
spock21groovy25Implementation project(':spock-mockable')
spock21groovy25Implementation 'org.spockframework:spock-core'
spock21groovy25Implementation 'org.slf4j:slf4j-simple:2.0.2'
spock21groovy25Implementation project(path: ':spock-mockable', configuration: 'shadow')
spock21groovy25Implementation 'org.objenesis:objenesis:3.3'
spock21groovy25Implementation 'org.slf4j:slf4j-simple:2.0.2'
spock21groovy25Implementation 'org.spockframework:spock-core'

spock21groovy30Implementation enforcedPlatform('org.codehaus.groovy:groovy-bom:3.0.9')
spock21groovy30Implementation enforcedPlatform('org.codehaus.groovy:groovy-bom:3.0.13')
spock21groovy30Implementation enforcedPlatform('org.spockframework:spock-bom:2.1-groovy-3.0')
spock21groovy30Implementation project(':spock-mockable')
spock21groovy30Implementation 'org.spockframework:spock-core'
spock21groovy30Implementation 'org.slf4j:slf4j-simple:2.0.2'
spock21groovy30Implementation project(path: ':spock-mockable', configuration: 'shadow')
spock21groovy30Implementation 'org.objenesis:objenesis:3.3'
spock21groovy30Implementation 'org.slf4j:slf4j-simple:2.0.2'
spock21groovy30Implementation 'org.spockframework:spock-core'

spock22groovy25Implementation enforcedPlatform('org.codehaus.groovy:groovy-bom:2.5.18')
spock22groovy25Implementation enforcedPlatform('org.spockframework:spock-bom:2.2-groovy-2.5')
spock22groovy25Implementation project(':spock-mockable')
spock22groovy25Implementation 'org.spockframework:spock-core'
spock22groovy25Implementation 'org.slf4j:slf4j-simple:2.0.2'
spock22groovy25Implementation project(path: ':spock-mockable', configuration: 'shadow')
spock22groovy25Implementation 'org.objenesis:objenesis:3.3'
spock22groovy25Implementation 'org.slf4j:slf4j-simple:2.0.2'
spock22groovy25Implementation 'org.spockframework:spock-core'

spock22groovy30Implementation enforcedPlatform('org.codehaus.groovy:groovy-bom:3.0.9')
spock22groovy30Implementation enforcedPlatform('org.codehaus.groovy:groovy-bom:3.0.13')
spock22groovy30Implementation enforcedPlatform('org.spockframework:spock-bom:2.2-groovy-3.0')
spock22groovy30Implementation project(':spock-mockable')
spock22groovy30Implementation project(path: ':spock-mockable', configuration: 'shadow')
spock22groovy30Implementation 'org.objenesis:objenesis:3.3'
spock22groovy30Implementation 'org.slf4j:slf4j-simple:2.0.2'
spock22groovy30Implementation 'org.spockframework:spock-core'
spock22groovy30Implementation 'org.objenesis:objenesis:3.3'

spock22groovy40Implementation enforcedPlatform('org.apache.groovy:groovy-bom:4.0.5')
spock22groovy40Implementation enforcedPlatform('org.apache.groovy:groovy-bom:4.0.6')
spock22groovy40Implementation enforcedPlatform('org.spockframework:spock-bom:2.2-groovy-4.0')
spock22groovy40Implementation project(':spock-mockable')
spock22groovy40Implementation 'org.spockframework:spock-core'
spock22groovy40Implementation 'org.slf4j:slf4j-simple:2.0.2'
spock22groovy40Implementation project(path: ':spock-mockable', configuration: 'shadow')
spock22groovy40Implementation 'org.objenesis:objenesis:3.3'
spock22groovy40Implementation 'org.slf4j:slf4j-simple:2.0.2'
spock22groovy40Implementation 'org.spockframework:spock-core'

spock23groovy25Implementation enforcedPlatform('org.codehaus.groovy:groovy-bom:2.5.18')
spock23groovy25Implementation enforcedPlatform('org.spockframework:spock-bom:2.3-groovy-2.5')
spock23groovy25Implementation project(':spock-mockable')
spock23groovy25Implementation 'org.spockframework:spock-core'
spock23groovy25Implementation 'org.slf4j:slf4j-simple:2.0.2'
spock23groovy25Implementation project(path: ':spock-mockable', configuration: 'shadow')
spock23groovy25Implementation 'org.objenesis:objenesis:3.3'
spock23groovy25Implementation 'org.slf4j:slf4j-simple:2.0.2'
spock23groovy25Implementation 'org.spockframework:spock-core'

spock23groovy30Implementation enforcedPlatform('org.codehaus.groovy:groovy-bom:3.0.9')
spock23groovy30Implementation enforcedPlatform('org.codehaus.groovy:groovy-bom:3.0.13')
spock23groovy30Implementation enforcedPlatform('org.spockframework:spock-bom:2.3-groovy-3.0')
spock23groovy30Implementation project(':spock-mockable')
spock23groovy30Implementation project(path: ':spock-mockable', configuration: 'shadow')
spock23groovy30Implementation 'org.objenesis:objenesis:3.3'
spock23groovy30Implementation 'org.slf4j:slf4j-simple:2.0.2'
spock23groovy30Implementation 'org.spockframework:spock-core'
spock23groovy30Implementation 'org.objenesis:objenesis:3.3'

spock23groovy40Implementation enforcedPlatform('org.apache.groovy:groovy-bom:4.0.5')
spock23groovy40Implementation enforcedPlatform('org.apache.groovy:groovy-bom:4.0.6')
spock23groovy40Implementation enforcedPlatform('org.spockframework:spock-bom:2.3-groovy-4.0')
spock23groovy40Implementation project(':spock-mockable')
spock23groovy40Implementation 'org.spockframework:spock-core'
spock23groovy40Implementation 'org.slf4j:slf4j-simple:2.0.2'
spock23groovy40Implementation project(path: ':spock-mockable', configuration: 'shadow')
spock23groovy40Implementation 'org.objenesis:objenesis:3.3'
spock23groovy40Implementation 'org.slf4j:slf4j-simple:2.0.2'
spock23groovy40Implementation 'org.spockframework:spock-core'
}

spock2x.forEach { source ->
Expand All @@ -111,6 +111,12 @@ test {
enabled = false
}

tasks.withType(GroovyCompile) {
// groovyOptions.fork = true
// groovyOptions.forkOptions.jvmArgs = ['-agentlib:jdwp=transport=dt_socket,server=n,address=5005,suspend=y']
}

tasks.withType(Test) {
forkEvery 1
// jvmArgs = ['-agentlib:jdwp=transport=dt_socket,server=n,address=localhost:5006,suspend=y']
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package io.github.joke.spockmockable.tests

import io.github.joke.spockmockable.Mockable
import org.spockframework.mock.MockUtil
import spock.lang.Specification

@Mockable(packages = 'io.github.joke.spockmockable.tests')
class PersonTestWithMockablePackage extends Specification {
class MockTest extends Specification {

def mockUtil = new MockUtil()

Expand Down
Loading

0 comments on commit fb03556

Please sign in to comment.