New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for mocking final classes/methods using ByteBuddy #735

Open
leonard84 opened this Issue May 26, 2017 · 12 comments

Comments

Projects
None yet
7 participants
@leonard84
Member

leonard84 commented May 26, 2017

Mockito 2 has experimental support for mocking final classes/methods (https://github.com/mockito/mockito/wiki/What%27s-new-in-Mockito-2#unmockable). Since Spock as of 1.1 also supports ByteBuddy, it should be possible to also support mocking of final classes/methods.

@leonard84

This comment has been minimized.

Show comment
Hide comment
@leonard84

leonard84 May 26, 2017

Member

@raphw what are your thoughts about this? How difficult would it be to add this to Spock?

Member

leonard84 commented May 26, 2017

@raphw what are your thoughts about this? How difficult would it be to add this to Spock?

@raphw

This comment has been minimized.

Show comment
Hide comment
@raphw

raphw May 29, 2017

Contributor

This would absolutely be possible. The way to do it is about as follows:

  1. Attach a Java agent to the current process to get hold of an instance of Instrumentation. This can be done easily by the byte-buddy-agent class.
  2. To mock a class, define any type in the hierarchy of this class to implement the mocking logic inlined into any method of this class. The logic should look something like the following:
class Foo {
  void bar() {
    if (SpockDispatcher.isMock(this)) {
      SpockDispatcher.doMock(this);
    }  else {
      /* original code */
    }
  }
}

This can be done fairly easily by using the Advice component in Byte Buddy. It works similar to the current interception logic, only that the code is copied to any method. A redefinition can be done by registering a ClassFileTransformer to the instrumentation instance and to redefine the mock classes using it.
3. To make the SpockDispatcher above available to any class loader, it needs to be in its own artifact and must be injected into the bootstrap class loader prior to mocking the first class. This can be done easily by the instrumentation instance.

There are some more details to take care of; but those are not to difficult to solve. I can certainly help with the implementation, the Mockito InlineMockMaker should serve as a good blueprint. I am currently a bit busy with Java 9 support in Byte Buddy, but please ping me if you want to implement it.

Contributor

raphw commented May 29, 2017

This would absolutely be possible. The way to do it is about as follows:

  1. Attach a Java agent to the current process to get hold of an instance of Instrumentation. This can be done easily by the byte-buddy-agent class.
  2. To mock a class, define any type in the hierarchy of this class to implement the mocking logic inlined into any method of this class. The logic should look something like the following:
class Foo {
  void bar() {
    if (SpockDispatcher.isMock(this)) {
      SpockDispatcher.doMock(this);
    }  else {
      /* original code */
    }
  }
}

This can be done fairly easily by using the Advice component in Byte Buddy. It works similar to the current interception logic, only that the code is copied to any method. A redefinition can be done by registering a ClassFileTransformer to the instrumentation instance and to redefine the mock classes using it.
3. To make the SpockDispatcher above available to any class loader, it needs to be in its own artifact and must be injected into the bootstrap class loader prior to mocking the first class. This can be done easily by the instrumentation instance.

There are some more details to take care of; but those are not to difficult to solve. I can certainly help with the implementation, the Mockito InlineMockMaker should serve as a good blueprint. I am currently a bit busy with Java 9 support in Byte Buddy, but please ping me if you want to implement it.

@henrik242

This comment has been minimized.

Show comment
Hide comment
@henrik242

henrik242 Sep 29, 2017

Contributor

@raphw @leonard84 Is there any progress on this? I'm trying to use Spock on Kotlin code, and their decision to make objects and data classes always final is painful when testing.

Contributor

henrik242 commented Sep 29, 2017

@raphw @leonard84 Is there any progress on this? I'm trying to use Spock on Kotlin code, and their decision to make objects and data classes always final is painful when testing.

@leonard84

This comment has been minimized.

Show comment
Hide comment
@leonard84

leonard84 Sep 29, 2017

Member

@henrik242 no, there are some other issues we are working on with higher priority right now.

Member

leonard84 commented Sep 29, 2017

@henrik242 no, there are some other issues we are working on with higher priority right now.

@clydzik

This comment has been minimized.

Show comment
Hide comment
@clydzik

clydzik Nov 10, 2017

will just confirm that android+kotlin+spock is a heavy issue with it.
probably the same will be with springboot applicaitons that are written in kotlin. any class/method mocked need to be open

clydzik commented Nov 10, 2017

will just confirm that android+kotlin+spock is a heavy issue with it.
probably the same will be with springboot applicaitons that are written in kotlin. any class/method mocked need to be open

@leonard84

This comment has been minimized.

Show comment
Hide comment
@leonard84

leonard84 Nov 12, 2017

Member

@clydzik yes we are aware of that, if you are using spring you'd need to use the allopen plugin anyway. So in the meantime I'd suggest using it, this way you don't have to make your classes explicitly open.

Member

leonard84 commented Nov 12, 2017

@clydzik yes we are aware of that, if you are using spring you'd need to use the allopen plugin anyway. So in the meantime I'd suggest using it, this way you don't have to make your classes explicitly open.

@henrik242

This comment has been minimized.

Show comment
Hide comment
@henrik242

henrik242 Nov 13, 2017

Contributor

The allopen plugin will make all spring-annotated classes non-final, but for other classes you still have to use the open keyword. Or create a new annotation (e.g. com.example.Open), and annotate your classes with it. And a data class needs the annotation in order to be non-final, since it doesn't support the open keyword.

Contributor

henrik242 commented Nov 13, 2017

The allopen plugin will make all spring-annotated classes non-final, but for other classes you still have to use the open keyword. Or create a new annotation (e.g. com.example.Open), and annotate your classes with it. And a data class needs the annotation in order to be non-final, since it doesn't support the open keyword.

@clydzik

This comment has been minimized.

Show comment
Hide comment
@clydzik

clydzik Nov 13, 2017

@leonard84 thank You for clarification.
My actual scope is android. And i will probably use an open annotation temporary but still using this feature for test purposes can be seen as 'turning of language feature for tests'. That is why i'm pushing this forward.

I would be grateful if You could address for a solution in close future. And thank You for fast response.

clydzik commented Nov 13, 2017

@leonard84 thank You for clarification.
My actual scope is android. And i will probably use an open annotation temporary but still using this feature for test purposes can be seen as 'turning of language feature for tests'. That is why i'm pushing this forward.

I would be grateful if You could address for a solution in close future. And thank You for fast response.

@kriegaex

This comment has been minimized.

Show comment
Hide comment
@kriegaex

kriegaex Jan 24, 2018

For the benefit of anyone reading this I have a workaround, see also section "Update 2" of my answer on StackOverflow. There is also sample code there. I am quoting the essential part from there:

Add this to your Maven build (if you use Gradle, use something similar):

<dependency>
  <groupId>de.jodamob.kotlin</groupId>
  <artifactId>kotlin-runner-spock</artifactId>
  <version>0.3.1</version>
  <scope>test</scope>
</dependency>

Now you can use the SpotlinTestRunner from project kotlin-testrunner in combination with annotations like

  • @OpenedClasses([Foo, Bar, Zot])
  • @OpenedPackages(["de.scrum_master.stackoverflow", "my.other.package"])

and just use normal Spock mocks for final Java or Kotlin classes.

I would still love to see this in Spock, just like mocking static methods in Java classes. This way I would no longer need additional tools like the one above or PowerMock. BTW, I do know that using things like mocking static methods is a smell. But sometimes we have to use nasty 3rd party libs and test integration with them somehow...

kriegaex commented Jan 24, 2018

For the benefit of anyone reading this I have a workaround, see also section "Update 2" of my answer on StackOverflow. There is also sample code there. I am quoting the essential part from there:

Add this to your Maven build (if you use Gradle, use something similar):

<dependency>
  <groupId>de.jodamob.kotlin</groupId>
  <artifactId>kotlin-runner-spock</artifactId>
  <version>0.3.1</version>
  <scope>test</scope>
</dependency>

Now you can use the SpotlinTestRunner from project kotlin-testrunner in combination with annotations like

  • @OpenedClasses([Foo, Bar, Zot])
  • @OpenedPackages(["de.scrum_master.stackoverflow", "my.other.package"])

and just use normal Spock mocks for final Java or Kotlin classes.

I would still love to see this in Spock, just like mocking static methods in Java classes. This way I would no longer need additional tools like the one above or PowerMock. BTW, I do know that using things like mocking static methods is a smell. But sometimes we have to use nasty 3rd party libs and test integration with them somehow...

@leonard84

This comment has been minimized.

Show comment
Hide comment
@leonard84

leonard84 Jan 24, 2018

Member

@kriegaex thanks for the info

Member

leonard84 commented Jan 24, 2018

@kriegaex thanks for the info

@RaviH

This comment has been minimized.

Show comment
Hide comment
@RaviH

RaviH Feb 27, 2018

+1 on this issue. I LOVE Spock for many, many reasons, and I would like to not have to use another library like mockk https://github.com/oleksiyp/mockk (which by the way mocks Kotlin's Object i.e. Singleton etc)

RaviH commented Feb 27, 2018

+1 on this issue. I LOVE Spock for many, many reasons, and I would like to not have to use another library like mockk https://github.com/oleksiyp/mockk (which by the way mocks Kotlin's Object i.e. Singleton etc)

@sescotti

This comment has been minimized.

Show comment
Hide comment
@sescotti

sescotti Jun 11, 2018

+1 on this one. I just ran into this problem while trying to mock a final method (unfortunately from a third-party lib I cannot modify), and it'd be nice to have it. Otherwise I'll just have to fall back to Mockito (which I didn't want since it becomes increasingly verbose), as I don't want to have a lot of complimentary plugins and dependencies.
Is there any ETA about supporting this? To understand timings and considering that I'm working with Kotlin, to check if Spock will be a viable option to write the tests. Thanks!

sescotti commented Jun 11, 2018

+1 on this one. I just ran into this problem while trying to mock a final method (unfortunately from a third-party lib I cannot modify), and it'd be nice to have it. Otherwise I'll just have to fall back to Mockito (which I didn't want since it becomes increasingly verbose), as I don't want to have a lot of complimentary plugins and dependencies.
Is there any ETA about supporting this? To understand timings and considering that I'm working with Kotlin, to check if Spock will be a viable option to write the tests. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment