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

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?

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 the team at Google: https://testing.googleblog.com/2019/12/testing-on-toilet-tests-too-dry-make.html

Related

FormData with ExpressJS with image and body data

Hello I want to ask a question about using form data to send a file and also update data from user at the same time in ExpressJS. I want to ask if this could be handle all in one using form data (save image and update a profile information) through axios or should it be handle in two (two HTTP Requests)? For example:
formData.append('file', {
uri: file.uri,
type: file.type,
name: file.name,
});
// and append for example a updated info
formData.append('profileUpdate', {
name: newName,
email: newEmail,
age: newAge,
});
then receive it on the express route side and separate each other or can you even do it with form-data request? or is there a way I can send the body apart on a PUT or POST Request (form-data)
Thank you in advance!

Karate API : Passing variables to other feature file is not working

I am calling login feature file from other feature file from where I am passing url, username and password but it is not working for me. I am not using Background key here and i do not want also.
#CallAnotherFeature
Feature: Call Login Feature
Scenario: Calling Login Test
* def config = { endPointURL: 'https://qa1.testurl.com/login',username: 'user123', password: 'password123' }
* def result= call read('Login.feature') config
* print result.response
* print 'Sign In-'+signIn
* print 'Sign In Reponse-'+signIn.response
Feature: Login Feature
Scenario: Test Login for different users
* print 'Starting Test','#(endPointURL)'
Given url '#(endPointURL)'
* print 'user name','#(username)'
* print 'Password ','#(password)'
#And form field username = '#(username)'
#And form field password = '#(password)'
And request { username: '#(username)', password: '#(password)'}
When method post
Then status 200
* print response
* match response.loginSuccess == true
In Login.feature I tried to pass username and password as form data also even though these did not work. Could someone tell me what mistake I am making here.
I am using latest karate version 0.9.0
I see a few issues in your scripts,
1. Call Login Feature
1.1) I don't see signIn variable initialized anywhere in this feature nor from your login feature but you are trying to print it.
1.2) = Should be placed properly ;)
* def result = call read('Login.feature') config
2. Login Feature
2.1) I think you misunderstood the concept of embedded expressions. only for templating it into a JSON you may use it. but for calling it you can simply use the variable name.
eg.
Given url endPointURL
And form field username = username
And request { username: '#(username)', password: '#(password)'}
NOT
Given url '#(endPointURL)'
And form field username = '#(username)'
I will be more clear for you if you read the karate documentation from here -> karate Doc and refer karate Demos

New to api testing pre-request scripts - Automatically getting access token with OAuth 2.0 Grant Type 'Client Credentials'

I have been stuck for days on this and looked through many articles, but can not find a script that can help me.
The basis of the script is to automatically get authorization token, before i use a POST method.
As said before when getting a access token for this particular api the grant type is Client Crentials and the following fields are needed when manually getting the token :-
Token Name, Grant Type, Access Token URL, Client ID, Client Secrect, Scope and Client Authentication.
Is there a simple script that i can do this for me before actually doing the POST as it tiresome manually getting the token.
Thanks in advance with any help.
Kind Regards
Just an update i have found a way of actually getting the token now , so if you do the following.
Add a new request
Select 'Post'
Enter the api url
Click 'Body'
Click 'x-www-form-urlencoded'
I entered the following 'Keys'(enter your own corresponding 'values') - 'client_id', 'client_secret', 'scope' and 'grant type'
Click 'Send'
This will get you your token, i now need to find a way to either extract the token in a new request or find a way of putting this in the pre-request scripts, so I am able to enter the data need as 'raw' JSON.
Again if anyone can help, would appreciate it.
Kind Regards
Would this be any help to you? Or at least get you closer to what you need?
If you add this script to the Collection level pre-request script it will get the token and set this as the jwt variable. You can use this variable in the Headers for the main requests, using the {{jwt}} syntax - This script also gets the expiry_in value from the token response and sets this as a variable.
On each request in the collection, it will run the script and check to see if you have the AccessTokenExpiry and jwt properties in the environment file, it also checks to see if the token has expired. If any of those statements are true, it will get another token for you. If those are ok, it will use what you have set.
const moment = require('moment')
const getJWT = {
url: `<your token base path>/Auth/connect/token`,
method: 'POST',
header: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: {
mode: 'urlencoded',
urlencoded: [
{key: 'grant_type', value: 'client_credentials'},
{key: 'scope', value: '<scope details>'}
{key: 'client_id', value: 'your creds'}
{key: 'client_secret', value: 'your creds'}
]
}
}
var getToken = true
if (!_.has(pm.environment.toObject(), 'AccessTokenExpiry')
|| !_.has(pm.environment.toObject(), 'jwt')
|| pm.environment.get('AccessTokenExpiry') <= moment().valueOf()) {
} else {
getToken = false
}
if (getToken) {
pm.sendRequest(getJWT, (err, res) => {
if (err === null) {
pm.environment.set('jwt', `Bearer ${res.json().access_token}`)
var expiryDate = moment().add(res.json().expires_in, 's').valueOf()
pm.environment.set('AccessTokenExpiry', expiryDate)
}
})
}
To access the Collection level elements, if you hover over the collection name and click the ... icon, this will display a list of menu options. Select edit.

How to put parameters in JSON in feature (karate framework)?

I would like to send my parameters from:
* def d = call read ('datas.json')
in my method in js file:
* header Authorization = call read('basic-auth.js') { username: 'd.usn', password: 'd.pw' }
(assuming that in datas.json I have usn and pw)
instead of writing parameters 'john' and 'secret'
* header Authorization = call read('basic-auth.js') { username: 'john', password: 'secret' }
I'm sorry if I didn't find the information in official repo, but any answer would be very helpful: I needed this several times and didn't find an issue.
You make me worry that the Karate documentation is useless :P. Did you look at Embedded Expressions ?
Authorization = call read('basic-auth.js') { username: '#(d.usn)', password: '#(d.pw)' }
By the way if datas.json was already in the form : { username: 'john', password: 'secret' }, you could do this:
Authorization = call read('basic-auth.js') read('datas.json')

Google Maps Directions API using REST with Groovy

#Grab('com.github.groovy-wslite:groovy-wslite:1.1.2')
import wslite.rest.*
String key = 'my-key'
def client = new RESTClient('https://maps.googleapis.com/maps/api/directions/')
def response = client.get(path: 'xml',
query: [
origin: 'Disneyland',
destination: 'Universal Studios Hollywood',
sensor: 'false',
mode: 'driving',
key: 'my-key'
])
println response.DirectionsResponse.status
println response.DirectionsResponse.summary
I'm trying to use the Google API directions using REST with Groovy but I can't seem to print from response.DirectionsResponse. It would either output null or an error. Am I missing something? I tried searching everywhere for an answer but I can't find one.
Think you have 4 problems;
You need to pass accept: ContentType.XML to the get query
You need to use response.xml to get the parsed XML content of the response
You don't need DirectionsResponse as this is the root element of the document (pointed to already by response.xml)
summary is inside a node route
The following should work:
#Grab('com.github.groovy-wslite:groovy-wslite:1.1.2')
import wslite.rest.*
def client = new RESTClient("https://maps.googleapis.com/")
def response = client.get(
path: "/maps/api/directions/xml",
accept: ContentType.XML,
query: [
origin: 'Disneyland',
destination: 'Universal Studios Hollywood',
sensor: 'false',
mode: 'driving',
key: 'AIzaSyBioD99qXv43yHLb9EuxemPeMHA1drpiqw'
])
println response.xml.status
println response.xml.route.summary
PS: You probably want to generate a new key, as you've made that one public now, so many people could be using your allowance (if you're on the free tier), or your money (if you've paid for it)