Skip to content
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

Create Custom (Mock) Interaction Semantic #1949

Open
akefirad opened this issue Apr 27, 2024 · 10 comments
Open

Create Custom (Mock) Interaction Semantic #1949

akefirad opened this issue Apr 27, 2024 · 10 comments

Comments

@akefirad
Copy link

Is your feature request related to a problem?

I'd like to be able t define a custom (mock) interaction semantic for mocking external resources (e.g. MockServer or WireMock) in the then block.

Describe the solution you'd like

As a very simple example, let's say we have user-service which depends on email-service while creating a user for sending email-verification message to the user (i.e. an API call to email-service to send a verification email to the user email address). In a unit test the spec would be something like this:

def 'subject should create a user and send verification email'() {

  given:
    // EmailServiceClient is a feight client poining to email-service
    def emailService = Mock(EmailServiceClient)
    def subject = new UserService(emailService)
    def req = aCreateUserReq()

  when:
    def res = subject.createUser(req)

  then:
    res.status == 200

  and:
    1 * emailService.sendVerificationEmail(_)

}

Now ideally in a (integration) test of the feature I'd love to be able to write something like this:

def 'subject should create a user and send verification email'() {

  given:
    def req = aCreateUserReq() // Now subject is a real bean injected into the test!

  when:
    def res = subject.createUser(req)

  then:
    res.status == 200

  and:
    1 * wiremock.sendVerificationEmail(_) // sendVerificationEmail is just a helper method creating the WireMock stub!

}

Currently this is not gonna work, clearly! For start wiremock is not a mock object, and also the semantic of the interaction mocking is a bit different (in this case, the stub needs to be created right away, i.e. calling WireMock).
Theoretically this should be possible I think, but I don't have deep knowledge about Spock inner details to come up with a solution. Best case scenario is to support both subbing and verification, but for start we can focus on the stubbing part.

Describe alternatives you've considered

Currently I'm putting all wiremock stubs in the given block which is not idea since the rest of the mocks are in the then block. This becomes even uglier for tests with multiple when/then blocks, since the given block is a singleton, the next batch of wiremock stubs (needed for the next when block) has to either live in the given block (which is too far away from the feature being tested; hence less maintainability) or in the relevant when block (which decreases the readability).

Additional context

Let me know if there's anything I need to provide. Thanks.

PS: Great job BTW 👏 !

@Vampire
Copy link
Member

Vampire commented Apr 27, 2024

You can probably use an explicit interaction block to achieve what you want: https://spockframework.org/spock/docs/2.3/all_in_one.html#_explicit_interaction_blocks

@akefirad
Copy link
Author

Unfortunately that’s not gonna work since Spock moves only interactions to before the when block. As matter of fact, this is exactly what I’m asking as the feature request 🙂.

@Vampire
Copy link
Member

Vampire commented Apr 28, 2024

Did you have a look at the link I provided and the sneaky little word "explicit" I used?

@akefirad
Copy link
Author

akefirad commented Apr 28, 2024

Ah, sorry, missed the point. OK, I think it'll do the job for now (I'll try it now). But nevertheless, I still believe this could be a very nice feature; more specifically; number of invocations as well as auto verification. If I understand "explicit block" correctly, it doesn't help with the above limitation. LMK. Thanks.

@Vampire
Copy link
Member

Vampire commented Apr 28, 2024

That's right, it just helps defining the wiremock stubs in then and get it moved to before when

@leonard84
Copy link
Member

@akefirad can you provide a working end2end case of what you are currently doing and then how you'd see it working with the new feature?

@akefirad
Copy link
Author

akefirad commented May 22, 2024

There you go demo.zip
LMK if you need more information Thanks.

@akefirad
Copy link
Author

akefirad commented Jun 3, 2024

Another use case for this would be to just use Feign clients as the interface for the mock object. Let's say you have a Feign client like this:

@FeignClient
interface FooServiceClient {
    @GetMapping(path = ["/foo"])
    fun getFoo(): String
}

Then Ideally I would love to be able to do this:

def 'subject should create a user and send verification email'() {

  // This is a method similar to `Mock` of Spock,
  // which returns a special mock object
  def foo = WireMock(FooServiceClient)

  given:
    def req = someRequest()

  when:
    // Here the subject needs to call `http://foo-service/foo`
    def res = subject.doSomething(req)

  then:
    res.status == 200

  and:
    1 * foo.getFoo() >> someFooResponse()
    // Things get very interesting with the request has some params and/or body.
}

@akefirad
Copy link
Author

akefirad commented Jun 3, 2024

BTW, I'm happy to work on this if you like the idea. I just need a bit of help to get rolling. LMK.

@leonard84
Copy link
Member

I can't think of a good way to add generic support for this into Spock.
This is complicated by the fact that it has to be part of the AST transformations.

For context, the actual mock object never knows about a defined interaction such as 1 * foo.getFoo() >> someFooResponse(), instead it just intercepts the call and forwards it to the IMockController.handle(_), which knows about all interactions to find do the matching and return a result. This is done so that Spock can handle things like 0 * _._.

This does not mesh well with a framework such as WireMock, where you need to declare the interactions with WireMock beforehand instead of registering a callback to handle the interaction yourself.

In your example where would the conversion of 1 * foo.getFoo() >> someFooResponse() to something that WireMock understands happen?

        wiremock.stubFor(WireMock.get(WireMock.urlEqualTo("/bar"))
                    .willReturn(WireMock.aResponse()
                            .withStatus(200)
                            .withBody("Hello from WireMock!")))
        
        wiremock.verify(1, WireMock.getRequestedFor(WireMock.urlEqualTo("/bar")))

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

No branches or pull requests

3 participants