Support mocking project.exec(...) - currently impossible due to Groovy bug

This is a request to allow mocking of the project.exec(…) method (also project.copy and so on). This is important for validating calls to the command line. This is currently impossible as the Project class is an interface. When mocking is attempted using MockFor, it will always call the original method and never the mock. This is a long standing bug in Groovy. Tracked as GROOVY-3493 (reported originally in 2009):

“Cannot override methods via metaclass that are part of an interface implementation”

I believe this could be fixed if Project.groovy was changed so it was no longer an interface. I understand that it may have substantial implications which make this a difficult change. This problem may apply to other interfaces as well but Project was the most painful issue I encountered.

A more detailed description of why MockFor fails due to metaClass issues is described on this SO answer. It also describes a complex workaround using invokeMethod… which is effective but complex and hacky:

Here is the class:

https://github.com/gradle/gradle/blob/master/subprojects/core/src/main/groovy/org/gradle/api/Project.java#L203

I can assure you, that the suggested change won’t happen just to fix the mockFor issue. I would suggest to go with a different testing framework. Have a look on spock or/and have a look at the provided test fixture org.gradle.testfixtures.ProjectBuilder for your tests.

Looking around at Spock, it appears that this is worth trying. From the docs, it uses JDK Dynamic Proxies rather than metaClass programming:

http://spockframework.github.io/spock/docs/1.0/interaction_based_testing.html

Like most Java mocking frameworks, Spock uses JDK dynamic proxies (when mocking interfaces) and CGLIB proxies (when mocking classes) to generate mock implementations at runtime. Compared to implementations based on Groovy meta-programming, this has the advantage that it also works for testing Java code.