Recommended Akka Props style throws IllegalArgumentException only inside unit test - unit-testing

See unit test below. An Actor class defined inside and outside of the unit test itself appear to behave differently when being instantiated via via system.actorOf but only when using the Props style recommended in the Akka documentation (page 67, Section 3.1 Props of Akka Scala Documentation, Release 2.2.3).
I declare two simple Greeter Actors, one inside and one outside the spec, and then get ActorRefs for each one in two different ways -- once using the deprecated Props style and once using the recommended Props style. Only line [4] throws the IllegalArgumentException.
Any ideas what might be going on? Thanks for any help!
(Note: same issue arises using FunSpec so WordSpec, MustMatchers don't seem to be a factor)
import org.junit.runner.RunWith
import org.scalatest.WordSpec
import org.scalatest.matchers.MustMatchers
import akka.actor.ActorSystem
import akka.actor.Props
import org.scalatest.junit.JUnitRunner
import akka.actor.Actor
class MyGreeter extends Actor {
def receive = {
case _ => println("greetings!")
}
}
#RunWith(classOf[JUnitRunner])
class HelloAkkaTest extends WordSpec with MustMatchers {
class MyOtherGreeter extends Actor {
def receive = {
case _ => println("greetings!")
}
}
"Greeter" must {
"greet" in {
val system = ActorSystem()
system.actorOf(Props[MyGreeter], "greeter") // [1] Works
system.actorOf(Props(new Greeter)) // [2] Works
system.actorOf(Props(new MyOtherGreeter)) // [3] Works
system.actorOf(Props[MyOtherGreeter], "other") // [4] Fails!
}
}
}
I am using scalatest 2.10-2.0-RC3 with scala 2.10.2 and akka 2.10-2.3.0-RC2

What you face here is an interesting detail of scala and nested classes. When you have a construct like
class Foo {
class Bar
}
then the inner class Bar will be bound to a specific instance of Foo, meaning you can't instantiate it outside of this instance.
scala> new Foo
res5: Foo = Foo#5b2cef50
scala> new res5.Bar
res6: res5.Bar = Foo$Bar#64f8b658
scala> new Foo#Bar
<console>:12: error: Foo is not a legal prefix for a constructor
new Foo#Bar
^
The Props.apply[A <: Actor] implicitly retrieves an instance of ClassTag[A] an then tries to call the constructor through reflection. So let's have a look at the constructors of Foo#Bar:
scala> classOf[Foo#Bar].getConstructors
res9: Array[java.lang.reflect.Constructor[_]] = Array(public Foo$Bar(Foo))
As you can see, it expects an instance of Foo as parameter. So how do we fix this?
My suggestion would be to just take the class outside of the test class. There are 2 possibilities, either put it directly under the package, as your other class, or put in in the companion of the test class.
Another way to fix this would be to pass a reference to the test class to the Props:
system.actorOf(Props(classOf[MyOtherGreeter], this), "other")

Related

Spock unit testing assert log calls and see output

I am using spock to test Java Spring Boot code. It gets a logback logger over the lombok #Slf4j annotation.
Dummy class with log call
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
#Slf4j
#Component
public class Clazz {
public void method() {
// ... code
log.warn("message", new RuntimeException());
}
}
The Spock Spec
import groovy.util.logging.Slf4j
import org.junit.Rule
import org.slf4j.Logger
import spock.lang.Specification
#Slf4j
class LogSpec extends Specification {
Clazz clazz = new Clazz()
private Logger logger = Mock(Logger.class)
#Rule
ReplaceSlf4jLogger replaceSlf4jLogger = new ReplaceSlf4jLogger(Clazz, logger)
def "warning ia logged"() {
given: "expected message"
when: "when calling the method"
clazz.method()
then: "a warning is logged"
1 * logger.warn(_, _) >> {
msg, ex -> log.warn(msg, ex)
}
}
}
Helper to switch the real with the mock logger taken from this answer.
import org.junit.rules.ExternalResource
import org.slf4j.Logger
import java.lang.reflect.Field
import java.lang.reflect.Modifier
/**
* Helper to exchange loggers set by lombok with mock logger
*
* allows to assert log action.
*
* Undos change after test to keep normal logging in other tests.
*
* code from this answer answer
*/
class ReplaceSlf4jLogger extends ExternalResource {
Field logField
Logger logger
Logger originalLogger
ReplaceSlf4jLogger(Class logClass, Logger logger) {
logField = logClass.getDeclaredField("log")
this.logger = logger
}
#Override
protected void before() throws Throwable {
logField.accessible = true
Field modifiersField = Field.getDeclaredField("modifiers")
modifiersField.accessible = true
modifiersField.setInt(logField, logField.getModifiers() & ~Modifier.FINAL)
originalLogger = (Logger) logField.get(null)
logField.set(null, logger)
}
#Override
protected void after() {
logField.set(null, originalLogger)
}
}
I would like to test log calls, but still see the log message.
I am using the solution from this answer, it works for the assertion but I don't see the log because it is a mock call.
I came up with this solution, which does a the call with the logger of the groovy spec.
1 * logger.warn(_ , _) >> {
msg, ex -> log.warn(msg, ex)
}
But I find it verbose, any idea how I could create a helper function for it. I am not very familiar with functional groovy and moving this code into a function is not working.
I also tried a Spy instead of a Mock but that gets me an error because the logger class is final.
import ch.qos.logback.classic.Logger
private Logger logger = Spy(Logger.class)
>> org.spockframework.mock.CannotCreateMockException: Cannot create mock
for class ch.qos.logback.classic.Logger because Java mocks cannot mock final classes.
If the code under test is written in Groovy, use a Groovy mock.
Logger class at runtime
package ch.qos.logback.classic;
public final class Logger implements org.slf4j.Logger, LocationAwareLogger, AppenderAttachable<ILoggingEvent>, Serializable {
Thanks
Actually in your MCVE you expect the warn(_, _) method to be called with two parameters, but you are not logging like that in Clazz, so either you have to change Clazz to also log an exception or change the test to expect a method call with one parameter. I am doing the latter here.
As for your problem, the solution is to not use a mock but a spy. You need to tell Spock which exact class you want to spy on, though. This is because you cannot spy on an interface type, of course. I have chosen a SimpleLogger (change to whatever you use in your application).
package de.scrum_master.stackoverflow
import groovy.util.logging.Slf4j
import org.junit.Rule
import org.slf4j.impl.SimpleLogger
import spock.lang.Specification
#Slf4j
class LombokSlf4jLogTest extends Specification {
SimpleLogger logger = Spy(constructorArgs: ["LombokSlf4jLogTest"])
#Rule
ReplaceSlf4jLogger replaceSlf4jLogger = new ReplaceSlf4jLogger(Clazz, logger)
def "warning is logged"() {
when: "when calling the method"
new Clazz().method()
then: "a warning is logged"
1 * logger.warn(_)
}
}
Update: For what it is worth, here is a version which also works with LogBack-Classic instead of Log4J-Simple on the classpath. Instead of directly spying on the final class, let's just spy on a Groovy #Delegate:
Please also note that I changed to *_ in the test so as to accommodate to warn calls with an arbitrary number of arguments.
package de.scrum_master.stackoverflow
import groovy.util.logging.Slf4j
import org.junit.Rule
import org.slf4j.Logger
import spock.lang.Specification
#Slf4j
class LombokSlf4jLogTest extends Specification {
def logger = Spy(new LoggerDelegate(originalLogger: log))
#Rule
ReplaceSlf4jLogger replaceSlf4jLogger = new ReplaceSlf4jLogger(Clazz, logger)
def "warning is logged"() {
when: "when calling the method"
new Clazz().method()
then: "a warning is logged"
1 * logger.warn(*_)
true
}
static class LoggerDelegate {
#Delegate Logger originalLogger
}
}
Update 2020-01-23: I just found this one again and noticed that I forgot to explain why the #Delegate solution works: because a Groovy delegate automatically implements all interfaces which the class of the delegate instance also implements by default. In this case the logger field is declared as Logger which is an interface type. This is also why e.g. Log4J or Logback instances can be used based on the configuration. The trick of mocking or spying on a final class type not implementing an interface or used explicitly with its class name would not work in that case because the delegating class would not (and could not) be a subclass of the final class type and thus could not be injected instead of the delegate.
Update 2020-04-14: I did not mention before that if you don't want to spy on a real logger but simply use a dummy you can check interactions on, just use a regular Spock mock on the org.slf4j.Logger interface: def logger = Mock(Logger) That is actually the simplest solution and you don't clutter your test log with exception stack traces and other log output. I was so focused on helping the OP with his spy solution that I did not mention this before.
These is one more "creative" approach for this kind of issue I would like to share.
Instead of mocking the logger you can create an "artificial" appender, add it programmatically to the logger in the class under-test.
The appender will keep track of the logged messages and during the verification phase you will get those logged message and verify
You'll end up with something like this (pseudo code just to show the idea):
class MsgTrackingAppender implements Appender { // Appender of your logging system
private List<LogEvent> events = new ArrayList<>();
public void doAppend(LogEvent evt) {
events.add(evt);
}
public List<LogEvent> getEvents() {
return events;
}
}
// now in test you can do:
class LogSpec extends Specification {
def "test me"() {
given:
Clazz underTest = Clazz()
MsgTrackingAppender appender = new MsgTrackingAppender()
LogFactory.getLogger(Clazz.class).addAppender(appender)
when:
underTest.method()
then:
appender.events.size == 1
appender.events[0].level == Level.WARN
appender.events[0].message == ... // verify whatever you want on the messages
}
}
IMO this approach is easier to use than extensive mocking but its a matter of taste of course.

Unit testing of a class with StaticLoggerBinder

I do have a simple class like this:
package com.example.howtomocktest
import groovy.util.logging.Slf4j
import java.nio.channels.NotYetBoundException
#Slf4j
class ErrorLogger {
static void handleExceptions(Closure closure) {
try {
closure()
}catch (UnsupportedOperationException|NotYetBoundException ex) {
log.error ex.message
} catch (Exception ex) {
log.error 'Processing exception {}', ex
}
}
}
And I would like to write a test for it, here is a skeleton:
package com.example.howtomocktest
import org.slf4j.Logger
import spock.lang.Specification
import java.nio.channels.NotYetBoundException
import static com.example.howtomocktest.ErrorLogger.handleExceptions
class ErrorLoggerSpec extends Specification {
private static final UNSUPPORTED_EXCEPTION = { throw UnsupportedOperationException }
private static final NOT_YET_BOUND = { throw NotYetBoundException }
private static final STANDARD_EXCEPTION = { throw Exception }
private Logger logger = Mock(Logger.class)
def setup() {
}
def "Message logged when UnsupportedOperationException is thrown"() {
when:
handleExceptions {UNSUPPORTED_EXCEPTION}
then:
notThrown(UnsupportedOperationException)
1 * logger.error(_ as String) // doesn't work
}
def "Message logged when NotYetBoundException is thrown"() {
when:
handleExceptions {NOT_YET_BOUND}
then:
notThrown(NotYetBoundException)
1 * logger.error(_ as String) // doesn't work
}
def "Message about processing exception is logged when standard Exception is thrown"() {
when:
handleExceptions {STANDARD_EXCEPTION}
then:
notThrown(STANDARD_EXCEPTION)
1 * logger.error(_ as String) // doesn't work
}
}
The logger in ErrorLogger class is provided by StaticLoggerBinder, so my question is - how do I make it work so that those checks "1 * logger.error(_ as String)" would work? I can't find a proper way of mocking that logger inside of ErrorLogger class. I have thought about reflection and somehow accessing it, furthermore there was an idea with mockito injection (but how to do that if reference to an object is not even present in that class because of that Slf4j annotation!) Thanks in advance for all your feedback and advices.
EDIT: Here is an output of a test, even 1*logger.error(_) doesn't work.
Too few invocations for:
1*logger.error() (0 invocations)
Unmatched invocations (ordered by similarity):
What you would need to do is to replace the log field generated by the #Slf4j AST transformation with your mock.
However, this is not so easy to achieve, since the generated code is not really test-friendly.
A quick look at the generated code reveals that it corresponds to something like this:
class ErrorLogger {
private final static transient org.slf4j.Logger log =
org.slf4j.LoggerFactory.getLogger(ErrorLogger)
}
Since the log field is declared as private final it is not so easy to replace the value with your mock. It actually boils down to the exact same problem as described here. In addition, usages of this field is wrapped in isEnabled() methods, so for instance every time you invoke log.error(msg) it is replaced with:
if (log.isErrorEnabled()) {
log.error(msg)
}
So, how to solve this? I would suggest that you register an issue at the groovy issue tracker, where you ask for a more test-friendly implementation of the AST transformation. However, this won't help you much right now.
There are a couple of work-around solutions to this that you might consider.
Set the new field value in your test using the "awful hack" described in the stack overflow question mentioned above. I.e. make the field accessible using reflection and set the value. Remember to reset the value to the original during cleanup.
Add a getLog() method to your ErrorLogger class and use that method for access instead of direct field access. Then you may manipulate the metaClass to override the getLog() implementation. The problem with this approach is that you would have to modify the production code and add a getter, which kind of defies the purpose of using #Slf4j in the first place.
I'd also like to point out that there are several problems with your ErrorLoggerSpec class. These are hidden by the problems you've already encountered, so you would probably figure these out by yourself when they manifested themselves.
Even though it is a hack, I'll only provide code example for the first suggestion, since the second suggestion modifies the production code.
To isolate the hack, enable simple reuse and avoid forgetting to reset the value, I wrote it up as a JUnit rule (which can also be used in Spock).
import org.junit.rules.ExternalResource
import org.slf4j.Logger
import java.lang.reflect.Field
import java.lang.reflect.Modifier
public class ReplaceSlf4jLogger extends ExternalResource {
Field logField
Logger logger
Logger originalLogger
ReplaceSlf4jLogger(Class logClass, Logger logger) {
logField = logClass.getDeclaredField("log");
this.logger = logger
}
#Override
protected void before() throws Throwable {
logField.accessible = true
Field modifiersField = Field.getDeclaredField("modifiers")
modifiersField.accessible = true
modifiersField.setInt(logField, logField.getModifiers() & ~Modifier.FINAL)
originalLogger = (Logger) logField.get(null)
logField.set(null, logger)
}
#Override
protected void after() {
logField.set(null, originalLogger)
}
}
And here is the spec, after fixing all the small bugs and adding this rule. Changes are commented in the code:
import org.junit.Rule
import org.slf4j.Logger
import spock.lang.Specification
import java.nio.channels.NotYetBoundException
import static ErrorLogger.handleExceptions
class ErrorLoggerSpec extends Specification {
// NOTE: These three closures are changed to actually throw new instances of the exceptions
private static final UNSUPPORTED_EXCEPTION = { throw new UnsupportedOperationException() }
private static final NOT_YET_BOUND = { throw new NotYetBoundException() }
private static final STANDARD_EXCEPTION = { throw new Exception() }
private Logger logger = Mock(Logger.class)
#Rule ReplaceSlf4jLogger replaceSlf4jLogger = new ReplaceSlf4jLogger(ErrorLogger, logger)
def "Message logged when UnsupportedOperationException is thrown"() {
when:
handleExceptions UNSUPPORTED_EXCEPTION // Changed: used to be a closure within a closure!
then:
notThrown(UnsupportedOperationException)
1 * logger.isErrorEnabled() >> true // this call is added by the AST transformation
1 * logger.error(null) // no message is specified, results in a null message: _ as String does not match null
}
def "Message logged when NotYetBoundException is thrown"() {
when:
handleExceptions NOT_YET_BOUND // Changed: used to be a closure within a closure!
then:
notThrown(NotYetBoundException)
1 * logger.isErrorEnabled() >> true // this call is added by the AST transformation
1 * logger.error(null) // no message is specified, results in a null message: _ as String does not match null
}
def "Message about processing exception is logged when standard Exception is thrown"() {
when:
handleExceptions STANDARD_EXCEPTION // Changed: used to be a closure within a closure!
then:
notThrown(Exception) // Changed: you added the closure field instead of the class here
//1 * logger.isErrorEnabled() >> true // this call is NOT added by the AST transformation -- perhaps a bug?
1 * logger.error(_ as String, _ as Exception) // in this case, both a message and the exception is specified
}
}
If you are using Spring, you have acces to OutputCaptureRule
#Rule
OutputCaptureRule outputCaptureRule = new OutputCaptureRule()
def test(){
outputCaptureRule.getAll().contains("<your test output>")
}

specs2: Is there a way to use doNothing while mocking with Mockito?

Let's say that I have a mocked trait Foo:
trait Foo {
def op(x: String): Unit
}
and I mocked this interface using
val mockedFoo = mock[Foo]
I want the method op to throw an exception second time I call it, e.g.
import org.specs2.mock.Mockito
import org.specs2.mutable.Specification
trait Foo {
def op(x: String): Unit
}
class DummySpec extends Specification with Mockito {
"dummy" should {
"test" in {
val mockedFoo = mock[Foo]
org.mockito.Mockito.doNothing().doThrow(new RuntimeException).when(mockedFoo).op(any[String])
mockedFoo.op("This one should work fine") should not(throwAn[Exception])
mockedFoo.op("This one should throw an exception") should throwAn[Exception]
}
}
}
Is there a way to do this in specs2 style? e.g.
mockedFoo.op(any[String]) returns Unit thenThrows new RuntimeException
but this doesn't compile.
Thanks!
The Unit return type makes things a bit trickier as you can't just chain:
returns "foo" thenThrows new RuntimeException
But you can still solve this problem if you use answers like below:
mockedFoo.op(anyString) answers {args => } thenThrows new RuntimeException
See if this works for you.

How to mock private methods in controller in grails 2.2.4

I have a private method which was mocked in grails 1.3.7 using metaclass but now that I upgraded grails version to 2.2.4, the same mocking fails.
Method to test has a call to private method
private def MyPrivateMeth1(def arg1, def arg2) {
...
}
Mocking is something like this
MyController.metaClass.private.MyPrivateMeth1 = { a, b ->
...
}
Try using the #TestFor annotation, which will give you a controller variable. You can then alter the metaclass of that, as well as incorporating Kamil Mikolajczyk and araxn1d's suggestions. So, your whole test should probably look like this:
#TestFor(MyController)
class MyControllerTests {
setup() {
controller.metaClass.MyPrivateMeth1 = {def arg1, def arg2 ->
//you can make your mocked method return whatever you wish here
return [key1: "foo", key2: "bar"]
}
}
void testForSomething() {
//Some code here that invokes an action or two in your controller which in turn invokes the private method
}
}
Make sure to have def (or String, Long, or whatever declaration you use) on the arguments of your mock method precisely as they are on the actual private method in the controller, or your test will try to use the class's normal private method first.
Here's a similar test in Spock:
import spock.lang.Specification
import spock.lang.Unroll
import grails.test.mixin.*
import org.junit.*
#TestFor(MyController)
class MyControllerSpec {
def "test that thing"() {
setup:
controller.metaClass.MyPrivateMeth1 = {def arg1, def arg2 -> return someOutput }
expect:
controller.someAction() == expectedRender
where:
someOutput | expectedRender
[key1: "foo", key2: "bar"] | "expected render from controller"
}
}
It seems that you need to declare types of closure arguments (its 100% if that arguments have actual types, for example Long, but not sure about def, but you need to try):
MyController.metaClass.MyPrivateMeth1 = { def a, def b -> ... }
I believe you don't need the .private. part
MyController.metaClass.MyPrivateMeth1 = { a, b -> ... }
should be enough, however I would specify parameter types explicitly
And, by the way, you should keep java naming conventions - methods names should start with lowercase character
For unit tests I have used Reflection for private methods. Something similar to this should work...
Method method = BehaviourService.getDeclaredMethod("behaviourValidConstraints",User.class,Behaviour.class)
method.setAccessible(true)
boolean valid = ((Boolean)method.invoke(service, user,b)).booleanValue()
First you get the method with getDeclaredMethod setting the name and the parameter types, you set it accesible and finally tou call it with method.invoke passing the object that has the method and the parameters. The result is an Object so you have to cast it.
I know there must be a better solution, but this one is the only one I've found that works
Edit: Sorry, what's above is for making a call to a private method.
I think that I've mocked a private method before just doing...
MyController.metaClass.myPrivateMeth1 { a, b ->
...
}
Just like you wrote it but without the .private and the = sign. Also, as Kamil said, you should follow java naming conventions for method names...

Mock static method with GroovyMock or similar in Spock

First-timer here, apologies if I've missed anything.
I'm hoping to get around a call to a static method using Spock. Feedback would be great
With groovy mocks, I thought I'd be able to get past the static call but haven't found it.
For background, I'm in the process of retrofitting tests in legacy java. Refactoring is prohibited. I'm using spock-0.7 with groovy-1.8.
The call to the static method is chained with an instance call in this form:
public class ClassUnderTest{
public void methodUnderTest(Parameter param){
//everything else commented out
Thing someThing = ClassWithStatic.staticMethodThatReturnsAnInstance().instanceMethod(param);
}
}
staticMethod returns an instance of ClassWithStatic
instanceMethod returns the Thing needed in the rest of the method
If I directly exercise the global mock, it returns the mocked instance ok:
def exerciseTheStaticMock(){
given:
def globalMock = GroovyMock(ClassWithStatic,global: true)
def instanceMock = Mock(ClassWithStatic)
when:
println(ClassWithStatic.staticMethodThatReturnsAnInstance().instanceMethod(testParam))
then:
interaction{
1 * ClassWithStatic.staticMethodThatReturnsAnInstance() >> instanceMock
1 * instanceMock.instanceMethod(_) >> returnThing
}
}
But if I run the methodUnderTest from the ClassUnderTest:
def failingAttemptToGetPastStatic(){
given:
def globalMock = GroovyMock(ClassWithStatic,global: true)
def instanceMock = Mock(ClassWithStatic)
ClassUnderTest myClassUnderTest = new ClassUnderTest()
when:
myClassUnderTest.methodUnderTest(testParam)
then:
interaction{
1 * ClassWithStatic.staticMethodThatReturnsAnInstance() >> instanceMock
1 * instanceMock.instanceMethod(_) >> returnThing
}
}
It throws down a real instance of ClassWithStatic that goes on to fail in its instanceMethod.
Spock can only mock static methods implemented in Groovy. For mocking static methods implemented in Java, you'll need to use a tool like GroovyMock , PowerMock or JMockit.
PS: Given that these tools pull of some deep tricks in order to achieve their goals, I'd be interested to hear if and how well they work together with tests implemented in Groovy/Spock (rather than Java/JUnit).
Here is how I solved my similar issue (mocking a static method call which is being called from another static class) with Spock (v1.0) and PowerMock (v1.6.4)
import org.junit.Rule
import org.powermock.core.classloader.annotations.PowerMockIgnore
import org.powermock.core.classloader.annotations.PrepareForTest
import org.powermock.modules.junit4.rule.PowerMockRule
import spock.lang.Specification
import static org.powermock.api.mockito.PowerMockito.mockStatic
import static org.powermock.api.mockito.PowerMockito.when
#PrepareForTest([YourStaticClass.class])
#PowerMockIgnore(["javax.xml.*", "ch.qos.logback.*", "org.slf4j.*"])
class YourSpockSpec extends Specification {
#Rule
Powermocked powermocked = new Powermocked();
def "something something something something"() {
mockStatic(YourStaticClass.class)
when: 'something something'
def mocked = Mock(YourClass)
mocked.someMethod(_) >> "return me"
when(YourStaticClass.someStaticMethod(xyz)).thenReturn(mocked)
then: 'expect something'
YourStaticClass.someStaticMethod(xyz).someMethod(abc) == "return me"
}
}
The #PowerMockIgnore annotation is optional, only use it if there is some conflicts with existing libraries
A workaround would be to wrap the static method call into an instance method.
class BeingTested {
public void methodA() {
...
// was:
// OtherClass.staticMethod();
// replaced with:
wrapperMethod();
...
}
// add a wrapper method for testing purpose
void wrapperMethod() {
OtherClass.staticMethod();
}
}
Now you can use a Spy to mock out the static method.
class BeingTestedSpec extends Specification {
#Subject BeingTested object = new BeingTested()
def "test static method"() {
given: "a spy into the object"
def spyObject = Spy(object)
when: "methodA is called"
spyObject.methodA()
then: "the static method wrapper is called"
1 * spyObject.wrapperMethod() >> {}
}
}
You can also stub in canned response for the wrapper method if it's supposed to return a value. This solution uses only Spock built-in functions and works with both Java and Groovy classes without any dependencies on PowerMock or GroovyMock.
The way I've gotten around static methods in Groovy/Spock is by creating proxy classes that are substituted out in the actual code. These proxy classes simply return the static method that you need. You would just pass in the proxy classes to the constructor of the class you're testing.
Thus, when you write your tests, you'd reach out to the proxy class (that will then return the static method) and you should be able to test that way.
I have recently found 'spock.mockfree' package, it helps mocking final classes and static classes/methods.
It is quite simple as with this framework, in this case, you would need only to Spy() the class under test and #MockStatic the static method you need.
Example:
We used a static method returnA of StaticMethodClass class
public class StaticMethodClass {
public static String returnA() {
return "A";
}
}
here is the calling code
public class CallStaticMethodClass {
public String useStatic() {
return StaticMethodClass.returnA();
}
}
Now we need to test the useStatic method of CallStaticMethodClass class But spock itself does not support mock static methods, and we support
class CallStaticMethodClassTest extends Specification {
def 'call static method is mocked method'() {
given:
CallStaticMethodClass callStaticMethodClass = Spy()
println("useStatic")
expect:
callStaticMethodClass.useStatic() == 'M'
}
#MockStatic(StaticMethodClass)
public static String returnA() {
return "M";
}
}
We use the #MockStatic annotation to mark which class needs to be mocked
Directly implement the static method that requires mocking under it, the method signature remains the same, but the implementation is different.
Link to the framework:
https://github.com/sayweee/spock-mockfree/blob/498e09dc95f841c4061fa8224fcaccfc53904c67/README.md