spock
spock copied to clipboard
MockFactory via ServiceLoader
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.
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.
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.
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.
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.
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.
@leonard84 @kriegaex I have created a first draft PR #1746 on how such a MockFactory
can look like.