spock icon indicating copy to clipboard operation
spock copied to clipboard

MockFactory via ServiceLoader

Open leonard84 opened this issue 3 years ago • 5 comments

To let Sarek integrate into Spock, we need to offer a way register additional MockFactory instances, for this we should add a ServiceLoader for external MockFactories. The question is how we solve the problem of selecting which MockFactory to use in regards to the external MockFactories.

leonard84 avatar Sep 30 '20 12:09 leonard84

The most elegant way for the user to express her intention to use Sarek features would be implicitly via the Spock DSL, e.g. by

  • using global: true when creating a Java mock,
  • stubbing a static method for a Java target class,
  • creating a mock instance of a final class,
  • stubbing final methods of any class.

As Spock cannot do any of these without Sarek, it would be clear that - if Sarek is active - Spock would use it in order to achieve these things.

In some other cases, there is no Spock equivalent to some Sarek features yet, e.g.

  • obtaining a reference to a global mock instance not created by the user in the test but by instrumenting its constructor, which also registers each created instance in a queue from which it can be polled and then interacted with,
  • execute code before/after a constructor call
  • execute code before/after a static type initialiser (static block), possibly blocking the initialiser to run at all,
  • "un-final" classes and their methods while they are being loaded (unless we say it happens globally and transparently for each loaded class).

The alternative would be to make all of this explicit, adding more boilerplate to tests, e.g. users having to use more annotations or special classes and method calls. It depends on how ambitious we are with regard to a smooth user experience and seamless integration.

kriegaex avatar Oct 01 '20 04:10 kriegaex

I checked the code and the org.spockframework.mock.IMockFactory interface already has a canCreate(IMockConfiguration configuration) method. The CompositeMockFactory would just have to use the ServiceLoader api to load any IMockFactory and put them before the built-in JavaMockFactory/ GroovyMockFactory instances.

  • using global: true when creating a Java mock,

Sarek would then have to check if getImplementation() == MockImplementation.JAVA and isGlobal() == true in canCreate(IMockConfiguration configuration), maybe we need to extend the MockConfiguration class to make the options map available, so users could use sarek: true to indicate to use Sarek.

  • stubbing a static method for a Java target class,
  • creating a mock instance of a final class,
  • stubbing final methods of any class.

As this is unknown at the time of mock creation we can't take it into account, that is where sarek: true would come into play.

  • execute code before/after a constructor call

Wouldn't this be the same as mocking constructors with groovy spies?


  def "mock constructor"() {
    GroovySpy(Person, global: true)

    when:
    new Person("fred", 42)
    new Person("barney", 21)

    then:
    1 * new Person("fred", 42)
    1 * new Person("barney", 21)
    0 * _
  }

Is it un-finialization or de-finalization 🤔? Regardless, for the first iteration I would say a global flag via SystemProperty to enable it would be enough.

With regards to the other features, I'd say we start with the MVP of fitting in Sarek with the existing feature set.

leonard84 avatar Oct 01 '20 09:10 leonard84

For most things you said my template answer for the time being is: You are probably right. I was kinda busy lately, so I have not done anything with Sarek in the last two months and also not checked the Spock API details. Hence, the idea of an MVP is definitely a good one. The devil is always in the details, i.e. creating some verifiable facts and then inspecting & adapting is a good way of gaining more insights. So for now the ball is in my court. I can ask some more informed questions afterwards or as I am moving along that path. 😀

that is where sarek: true would come into play

Oh, you are thinking of a global flag, not anything seamless like auto-detection. But if Spock cannot determine that during mock creation, as you said, then I agree. I was thinking though, the whole information necessary to determine what type of mocking, stubbing (incl. type of target object) we are dealing with should be contained in the test code at the time Spock does its AST transformation magic. So it would "just" be a matter of determining it. Never having worked with Groovy ASTs, I could be wrong and my assumption too naive, though.

BTW, do you see a possibility to use Spock features as much as possible and only use Sarek where there is no other way, some kind of hybrid mocking/stubbing? For example, use a normal Spock mock for instance methods and a Sarek mock for static ones. Sarek's intent is not to fully replace Spock mocks (i.e. JDK proxies, ByteBuddy or CGLIB), even though of course there are functional intersections.

  • execute code before/after a constructor call

Wouldn't this be the same as mocking constructors with groovy spies?

Functionally similar maybe. Technically it is totally different, though. The fundamental difference is that it works for Java classes, which is why I listed it as a delta to Spock.

kriegaex avatar Oct 02 '20 00:10 kriegaex

So it would "just" be a matter of determining it.

Well, there are several challenges, the most pressing one that you don't necessarily have access to the classes to determine which call would be to a static method. Furthermore, it is not always easy to find the source of the mock creation. IMHO this would drastically complicate the already tricky AST-transforms, only to hide the sarek: true. I personally would actually prefer the explicit flag, same as global: true this indicates I'm doing something to work around a design flaw and shouldn't just happen.

BTW, do you see a possibility to use Spock features as much as possible and only use Sarek where there is no other way, some kind of hybrid mocking/stubbing?

I'm not sure I'm understanding you correctly, as far as I see it, you decide on mock construction if you want a Sarek or a standard mock. However, you do this for each mock individually.

execute code before/after a constructor call

Wouldn't this be the same as mocking constructors with groovy spies?

Functionally similar maybe. Technically it is totally different, though. The fundamental difference is that it works for Java classes, which is why I listed it as a delta to Spock.

What I meant the is syntax in spock, Sarek could accept the same as what is currently done for global GroovyMocks.

leonard84 avatar Oct 02 '20 15:10 leonard84

I personally would actually prefer the explicit flag, same as global: true this indicates I'm doing something to work around a design flaw and shouldn't just happen.

I guess you are referring to a design flaw with regard to testability as such in the code under test or in one of its dependencies, aren't you?

BTW, do you see a possibility to use Spock features as much as possible and only use Sarek where there is no other way, some kind of hybrid mocking/stubbing?

I'm not sure I'm understanding you correctly, as far as I see it, you decide on mock construction if you want a Sarek or a standard mock. However, you do this for each mock individually.

I still have not started writing a Spock mock factory for Sarek, so we can ignore this question until further notice. I shall ask again if this is even necessary. I just had this idea on my mind to make Spock use Sarek like a surgical blade (as little as possible), even inside a (partial) Sarek mock, not like an axe (either Spock mock or Sarek does everything). Like I said, Spock could use its own mocks for instance methods but Sarek for static methods or even something like:

  • If the class is final and not loaded yet, use Sarek for unfinalisation (this is what I call it, but I mean the same as what you call definalisation).
  • The user creates a mock and stubs a static and an instance method.
  • Spock under the hood creates a normal Spock mock for instance method mocking and also uses Sarek in order to stub static methods. It would be transparent to the user, he would always just interact with one mock instance.
  • During the test, Spock delegates to either its own mock or to Sarek during the test, depending on which of the two methods is called. (I think this sounds more complicated than it really is.)

The alternatives would be either of:

  • The user creates a regular mock for stubbing the instance method and an additional Sarek mock just to take care of the static methods. (Sounds funny maybe, but OTOH this would be a nice separation of concerns. Maybe difficult to sell to users, though.)
  • The user creates a Sarek mock and Sarek has to take care of both static and instance methods.

I think the latter is what you had in mind, i.e. Sarek fully replacing Spock as an alternative mock engine, not merely being used for the missing delta to Spock like I imagined. Like I said, let us talk about it again when the need arises. I just wanted to explain my thoughts to you and document them for reference later.

kriegaex avatar Oct 03 '20 03:10 kriegaex

@leonard84 @kriegaex I have created a first draft PR #1746 on how such a MockFactory can look like.

AndreasTu avatar Aug 07 '23 13:08 AndreasTu