imbo / behat-api-extension

API extension for Behat, used to ease testing of JSON-based APIs
MIT License
107 stars 42 forks source link

Allow to use matcher function on a part of a value #103

Closed b2p-fred closed 2 years ago

b2p-fred commented 2 years ago

As of now, a matcher function cannot allow to filter a part of a value.

Given I have this json content:

  {
    "hydra:last": "/api/addresses?pagination=1&itemsPerPage=5&page=20"
  }

If I would like to get only the page count (e.g. 20), I expect to be able to use this syntax:

  {
    "hydra:last": "/api/addresses?pagination=1&itemsPerPage=5&page=@storeSet(pagesCount)"
  }

that will "extract" the page count part from the received value.

Indeed, my storeSet matcher function receives the complete Json value (/api/addresses?pagination=1&itemsPerPage=5&page=@storeSet(pagesCount)) and not the "filtered" part. Hence I am not able to get only the pages count part (20) ...

After some investigations in the source code, only the haystackValue and the function params are provided to the matcher function whereas it would be interesting to provide also the matched value.

I hacked the source code to make this solution run correctly. If I have some time in the next few days, I will propose a pull request

christeredvartsen commented 2 years ago

For this use case I would probably just use the @regExp matcher:

{
  "hydra:last": "@regExp(/page=20/)"
}
b2p-fred commented 2 years ago

I am sorry, but I think that me misunderstood. Perhaps I did not explained enough :wink:

The main intent behind my proposal is to be able to provide the matched content of the regex to the matcher function.

With the @regExp function I am able to check that the received JSON contains page=20. Ok ,great, but what if I want to get the page count and store it in the Behat context to re-use it later. My idea was to create a storeSet matcher function, correct ?

Indeed, the matcher function I created currently receives the whole value of the matched JSON content and not the part that is matched with the function signature !

    # 1. Check the page count is the expected value
    And the response body contains JSON:
    """
    {
      "hydra:last": "@regExp(/page\\=20/)"
    }
    """
    # 2. Get the page count and provide its value to the storeSet function
    And the response body contains JSON:
    """
    {
      "/api/addresses?pagination=1&itemsPerPage=5&page=@storeSet(pagesCount)"
    }
    """

With this syntax, I was able to implement a function that allows to store received information and then re-use them later in the Behat context of the scenario. If you are interest I may provide my context class with an example scenario ?

While testing a REST JSON API, this feature is really useful to get a newly created uuid and re-use it as an item IRI :wink:

christeredvartsen commented 2 years ago

Oh, OK. My bad, I was just thinking along the lines of matching.

If you could create a gist that shows some more context around this that would be great.

b2p-fred commented 2 years ago

For sure, I will create a gist :wink:

b2p-fred commented 2 years ago

@christeredvartsen : please see https://gist.github.com/b2p-fred/299814d71e798ffbce8a96eb300377c9. I tried to be clear but concise. The main magic is in the lines 34-38

christeredvartsen commented 2 years ago

I get what you are after now.

I have done pretty much the same thing by extending Imbo\BehatApiExtension\ContextApiContext, and added some custom steps for this purpose. There are some steps to achieve this though.

  1. Add the history middleware to the client in the extended context:

https://github.com/imbo/imbo/blob/main/features/bootstrap/FeatureContext.php#L194

  1. Create a custom step that refers to previous responses:

https://github.com/imbo/imbo/blob/a19003ee3fe6b87ed1317040ee185b5c9425f9d2/features/bootstrap/FeatureContext.php#L838-L855

So to translate this to your use case I would have created a step that was something along the lines of:

When I delete the previously added resource

and in that step I would simply pull out the last response in the history container, and get the ID from there, and do a request inline in the step implementation (like the one you can see in the code mentioned above).

I have actually been thinking of adding the history middleware directly in the feature context, so people can easily create custom steps that refers to previous requests. Personally I think this would be a better improvement since it opens up for a wider range of custom steps / logic in an extended context. This also makes the features themselves a bit easier to read since one can construct perfectly readable steps. In your gist the scenario contains a step that doesn't really test anything, it just stores some data that is used in the next step, and that, to me, doesn't feel like a good solution.

If you would like to have a go at adding the history middleware in the feature context that would be great, if not, I can see if I find the time to do it.

But feel free to copy the logic above and see if you can add the functionality you need using the history middleware and custom steps for instance.

b2p-fred commented 2 years ago

@christeredvartsen I will have a look to your proposal, thanks