How to compare 2 JSON objects that contains array using Karate tool and feature files

Files for the scenario

  • All the files are on same directory.

title-update-request.json

{id: 12, name: 'Old Hello', config:[{username: 'qwe', password: 'tyu'},{username: 'abc', password: 'xyz'}]}

title-update-response.json

{id: 12, name: 'New Hello', config:[{username: 'qwe', password: 'tyu'},{username: 'abc', password: 'xyz'}]}

title-update-error-request.json

{id: 00, name: 'Old Hello', config:[{username: 'qwe', password: 'tyu'},{username: 'abc', password: 'xyz'}]}

title-update-error-response.json

{Error: 'not found', Message: 'The provided Book is not found.'}

book-record.feature

Feature: CRUD operation on the book records.

Background:
        * def signIn = call read('classpath:login.feature')
        * def accessToken = signIn.accessToken
        * url baseUrl

 Scenario: Change title of book in the single book-record.
    * json ExpResObject = read('classpath:/book-records/title-update-response.json')
    * json ReqObject = read('classpath:/book-records/title-update-request.json')
    * call read('classpath:/book-records/update.feature') { Token: #(accessToken), ReqObj: #(ReqObject), ResObj: #(ExpResObject), StatusCode: 200 }

  Scenario: Change title of book in the non-existing book-record.
    * json ExpResObject = read('classpath:/book-records/title-update-error-request.json')
    * json ReqObject = read('classpath:/book-records/title-update-error-response.json')
    * call read('classpath:/book-records/update.feature') { Token: #(accessToken), ReqObj: #(ReqObject), ResObj: #(ExpResObject), StatusCode: 400 }

update.feature

Feature: Update the book record.

Scenario: Update single book-record.
    Given path '/book-record'
    And header Authorization = 'Bearer ' + __arg.Token
    And header Content-Type = 'application/json'
    And request __arg.ReqObj
    When method put
    Then status __arg.StatusCode
    And response == __arg.ExpectedResponse

Actual API response for scenario: 1 is :

{name: 'New Hello', config:[{username: 'abc', password: 'xyz'},{username: 'qwe', password: 'tyu'}]}

Actual API response for scenario: 2 is :

 {Error: 'not found', Message: 'The provided Book is not found.'}

Question: How should I validate the response in update.feature file since problem is if I make change s as using #^^config that will not works for scenario :2 and response == _arg.ExpectedResponse is not working for Scenario: 1?


Solution 1:

This is classic over-engineering of tests. If someone has told you that "re-use" is needed for tests, please don't listen to that person.

You have two scenarios, one happy path and one negative path. I am providing how you should write the negative path here below, the rest is up to you.

Scenario: Change title of book in the non-existing book-record.
Given path 'book-record'
And header Authorization = 'Bearer ' + accessToken
And request {id: 00, name: 'Old Hello', config:[{username: 'qwe', password: 'tyu'},{username: 'abc', password: 'xyz'}]}
When method put
Then status 400
And response == {Error: 'not found', Message: 'The provided Book is not found.'} 

See how clean it is ? There is no need for "extreme" re-use in tests. If you still insist that you want a super-generic re-usable feature file that will handle ALL your edge cases, you are just causing trouble for yourself. See how un-readable your existing tests have become !!

EDIT: Since I refer the question to others often as an example of how NOT to write tests, I wanted to make my point more clear and add a couple of links for reference.

Sometimes it is okay to "repeat yourself" in tests. Tests don't have to be DRY. Karate is a DSL that enables you to make HTTP calls or JSON manipulation in one or two lines. When you start attempting "re-use" like this, it actually leads to more harm than good. For example, you now need to look at multiple files to understand what your test is doing.

If you don't believe me, maybe you will believe Google: https://testing.googleblog.com/2019/12/testing-on-toilet-tests-too-dry-make.html