client-api
client-api copied to clipboard
HTTP REST API client for testing APIs based on the ruby’s RSpec framework that binds a complete api automation framework setup within itself
ClientApi
HTTP REST API client for testing application APIs based on the ruby’s RSpec framework that binds a complete api automation framework setup within itself
Features
- [x] Custom Header, URL, and Timeout support
- [x] URL query string customization
- [x] Datatype and key-pair value validation
- [x] Single key-pair response validation
- [x] Multi key-pair response validation
- [x] JSON response schema validation
- [x] JSON response content validation
- [x] JSON response size validation
- [x] JSON response is empty? validation
- [x] JSON response has specific key? validation
- [x] JSON response array-list sorting validation (descending, ascending)
- [x] Response headers validation
- [x] JSON template as body and schema
- [x] Support to store JSON responses of each tests for the current run
- [x] Logs support for debug
- [x] Custom logs remover
- [x] Auto-handle SSL for http(s) schemes
Installation
Add this line to your application's Gemfile:
gem 'client-api'
And then execute:
$ bundle
Or install it yourself as:
$ gem install client-api
Import the library in your env file
require 'client-api'
#Usage outline
Add this config snippet in the spec_helper.rb file:
ClientApi.configure do |config|
# all these configs are optional; comment out the config if not required
config.base_url = 'https://reqres.in'
config.headers = {'Content-Type' => 'application/json', 'Accept' => 'application/json'}
config.basic_auth = {'Username' => '[email protected]', 'Password' => 'myp@ssw0rd'}
config.json_output = {'Dirname' => './output', 'Filename' => 'test'}
config.time_out = 10 # in secs
config.logger = {'Dirname' => './logs', 'Filename' => 'test', 'StoreFilesCount' => 2}
# add this snippet only if the logger is enabled
config.before(:each) do |scenario|
ClientApi::Request.new(scenario)
end
end
Create client-api object with custom variable
api = ClientApi::Api.new
RSpec test scenarios look like,
it "GET request" do
api = ClientApi::Api.new
api.get('/api/users')
expect(api.status).to eq(200)
expect(api.code).to eq(200)
expect(api.message).to eq('OK')
end
it "POST request" do
api.post('/api/users', {"name": "prashanth sams"})
expect(api.status).to eq(201)
end
it "DELETE request" do
api.delete('/api/users/3')
expect(api.status).to eq(204)
end
it "PUT request" do
api.put('/api/users/2', {"data":{"email":"[email protected]","first_name":"Prashanth","last_name":"Sams"}})
expect(api.status).to eq(200)
end
it "PATCH request" do
api.patch('/api/users/2', {"data":{"email":"[email protected]","first_name":"Prashanth","last_name":"Sams"}})
expect(api.status).to eq(200)
end
# For exceptional cases with body in the GET/DELETE request
it "GET request with JSON body" do
api.get_with_body('/api/users', { "count": 2 })
expect(api.status).to eq(200)
end
it "DELETE request with JSON body" do
api.delete_with_body('/api/users', { "count": 2 })
expect(api.status).to eq(200)
end
# Customize URL query string as a filter
it "Custom URL query string" do
api.get(
{
:url => '/location?',
:query => {
'sort': 'name',
'fields[count]': '50',
'fields[path_prefix]': '6',
'filter[name]': 'Los Angels'
}
}
)
end
# For POST request with multi-form as body
it "POST request with multi-form as body" do
api.post('/api/upload',
payload(
'type' => 'multipart/form-data',
'data' => {
'file': './data/request/upload.png'
}
)
)
expect(api.code).to eq(200)
end
Validation shortcuts
Default validation
key features
- datatype validation
- key-pair value validation
- value size validation
- is value empty validation
- key exist or key not-exist validation
- single key-pair validation
- multi key-pair validation
what to know?
- operator field is optional when
"operator": "==" - exception is handled for the invalid key (say,
key: 'post->0->name'), if thehas_keyfield is not added in the validation
| General Syntax | Syntax | Model 2 | Syntax | Model 3 | Syntax | Model 4 | Syntax | Model 5 | Syntax | Model 6 |
|---|---|---|---|---|---|
validate(
api.body,
{
key: '',
operator: '',
value: '',
type: ''
}
)
|
validate(
api.body,
{
key: '',
size: 0
}
)
|
validate(
api.body,
{
key: '',
empty: true
}
)
|
validate(
api.body,
{
key: '',
has_key: true
}
)
|
validate(
api.body,
{
key: '',
operator: '',
value: '',
type: '',
size: 2,
empty: true,
has_key: false
}
)
|
validate(
api.body,
{
key: '',
type: ''
},
{
key: '',
operator: '',
value: ''
}
)
|
JSON response content validation
key benefits
- the most recommended validation for fixed / static JSON responses
- validates each JSON content value
what to know?
- replace
nullwithnilin the expected json (whenever applicable); cos, ruby don't know what isnull
| General Syntax | Syntax | Model 2 |
|---|---|
validate_json(
{
"data":
{
"id": 2,
"first_name": "Prashanth",
"last_name": "Sams",
}
},
{
"data":
{
"id": 2,
"first_name": "Prashanth",
"last_name": "Sams",
}
}
)
|
validate_json(
api.body,
{
"data":
{
"id": 2,
"first_name": "Prashanth",
"last_name": "Sams",
"link": nil
}
}
)
|
JSON response sorting validation
key benefits
- validates an array of response key-pair values with ascending or descending soring algorithm. For more details, check
sort_spec.rb
| General Syntax | Syntax | Model 2 |
|---|---|
validate_list(
api.body,
{
"key": "posts",
"unit": "id",
"sort": "ascending"
}
)
|
validate_list(
api.body,
{
"key": "posts",
"unit": "id",
"sort": "descending"
}
)
|
JSON response headers validation
key benefits
- validates any response headers
| General Syntax | Syntax | Model 2 |
|---|---|
validate_headers(
api.response_headers,
{
key: '',
operator: '',
value: ''
}
)
|
validate_headers(
api.response_headers,
{
key: "connection",
operator: "!=",
value: "open"
},{
key: "vary",
operator: "==",
value: "Origin, Accept-Encoding"
}
)
|
#General usage
Using json template as body
it "JSON template as body" do
api.post('/api/users', payload("./data/request/post.json"))
expect(api.status).to eq(201)
end
Add custom header
it "GET request with custom header" do
api.get('/api/users', {'Content-Type' => 'application/json', 'Accept' => 'application/json'})
expect(api.status).to eq(200)
end
it "PATCH request with custom header" do
api.patch('/api/users/2', {"data":{"email":"[email protected]","first_name":"Prashanth","last_name":"Sams"}}, {'Content-Type' => 'application/json', 'Accept' => 'application/json'})
expect(api.status).to eq(200)
end
Full url support
it "full url", :post do
api.post('https://api.enterprise.apigee.com/v1/organizations/ahamilton-eval',{},{'Authorization' => 'Basic YWhhbWlsdG9uQGFwaWdlZS5jb206bXlwYXNzdzByZAo'})
expect(api.status).to eq(403)
end
Basic Authentication
ClientApi.configure do |config|
...
config.basic_auth = {'Username' => '[email protected]', 'Password' => 'myp@ssw0rd'}
end
Custom Timeout in secs
ClientApi.configure do |config|
...
config.time_out = 10 # in secs
end
Output as json template
ClientApi.configure do |config|
...
config.json_output = {'Dirname' => './output', 'Filename' => 'sample'}
end
Logs
Logs are optional in this library; you can do so through config in
spec_helper.rb. The param,StoreFilesCountwill keep the custom files as logs; you can remove it, if not needed.
ClientApi.configure do |config|
...
config.logger = {'Dirname' => './logs', 'Filename' => 'test', 'StoreFilesCount' => 5}
config.before(:each) do |scenario|
ClientApi::Request.new(scenario)
end
end
#Validation | more info.
Single key-pair value JSON response validator
Validates JSON response value, datatype, size, is value empty?, and key exist?
validate(
api.body,
{
"key": "name",
"value": "prashanth sams",
"operator": "==",
"type": 'string'
}
)
Multi key-pair values response validator
Validates more than one key-pair values
validate(
api.body,
{
"key": "name",
"value": "prashanth sams",
"type": 'string'
},
{
"key": "event",
"operator": "eql?",
"type": 'boolean'
},
{
"key": "posts->1->enabled",
"value": false,
"operator": "!=",
"type": 'boolean'
},
{
"key": "profile->name->id",
"value": 2,
"operator": "==",
"type": 'integer'
},
{
"key": "profile->name->id",
"value": 2,
"operator": "<",
"type": 'integer'
},
{
"key": "profile->name->id",
"operator": ">=",
"value": 2,
},
{
"key": "post1->0->name",
"operator": "contains",
"value": "Sams"
},
{
"key": "post2->0->id",
"operator": "include",
"value": 34,
"type": 'integer'
},
{
"key": "post1->0->available",
"value": true,
"operator": "not contains",
"type": "boolean"
}
)
JSON response size validator
Validates the total size of the JSON array
validate(
api.body,
{
"key": "name",
"size": 2
},
{
"key": "name",
"operator": "==",
"value": "Sams",
"type": "string",
"has_key": true,
"empty": false,
"size": 2
}
)
JSON response value empty? validator
Validates if the key has empty value or not
validate(
api.body,
{
"key": "0->name",
"empty": false
},
{
"key": "name",
"operator": "==",
"value": "Sams",
"type": "string",
"size": 2,
"has_key": true,
"empty": false
}
)
JSON response has specific key? validator
Validates if the key exist or not
validate(
api.body,
{
"key": "0->name",
"has_key": true
},
{
"key": "name",
"operator": "==",
"value": "",
"type": "string",
"size": 2,
"empty": true,
"has_key": true
}
)
Operator
| Type | options |
|---|---|
| Equal | =, ==, eql, eql?, equal, equal? |
| Not Equal | !, !=, !eql, !eql?, not eql, not equal, !equal? |
| Greater than | >, >=, greater than, greater than or equal to |
| Less than | <, <=, less than, less than or equal to, lesser than, lesser than or equal to |
| Contains | contains, has, contains?, has?, include, include? |
| Not Contains | not contains, !contains, not include, !include |
Datatype
| Type | options |
|---|---|
| String | string, str |
| Integer | integer, int |
| Symbol | symbol, sym |
| Boolean | boolean, bool |
| Array | array, arr |
| Object | object, obj |
| Float | float |
| Hash | hash |
| Complex | complex |
| Rational | rational |
| Fixnum | fixnum |
| Falseclass | falseclass, false |
| Trueclass | trueclass, true |
| Bignum | bignum |
JSON response schema validation
validate_schema(
schema_from_json('./data/schema/get_user_schema.json'),
{
"data":
{
"id": 2,
"email": "[email protected]",
"firstd_name": "Janet",
"last_name": "Weaver",
"avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/josephstein/128.jpg"
}
}
)
validate_schema(
{
"required": [
"data"
],
"type": "object",
"properties": {
"data": {
"type": "object",
"required": [
"id", "email", "first_name", "last_name", "avatar"
],
"properties": {
"id": {
"type": "integer"
},
"email": {
"type": "string"
},
"first_name": {
"type": "string"
},
"last_name": {
"type": "string"
},
"avatar": {
"type": "string"
}
}
}
}
},
{
"data":
{
"id": 2,
"email": "[email protected]",
"first_name": "Janet",
"last_name": "Weaver",
"avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/josephstein/128.jpg"
}
}
)
validate_schema(
schema_from_json('./data/schema/get_user_schema.json'),
api.body
)
JSON response content validation
json response content value validation as a structure
actual_body = {
"posts":
{
"prashanth": {
"id": 1,
"title": "Post 1"
},
"sams": {
"id": 2,
"title": "Post 2"
}
},
"profile":
{
"id": 44,
"title": "Post 44"
}
}
validate_json( actual_body,
{
"posts":
{
"prashanth": {
"id": 1,
"title": "Post 1"
},
"sams": {
"id": 2
}
},
"profile":
{
"title": "Post 44"
}
})
validate_json( api.body,
{
"posts": [
{
"id": 2,
"title": "Post 2"
}
],
"profile": {
"name": "typicode"
}
}
)
Response headers validation
validate_headers(
api.response_headers,
{
key: "connection",
operator: "!=",
value: "open"
},
{
key: "vary",
operator: "==",
value: "Origin, Accept-Encoding"
}
)
Is there a demo available for this gem?
Yes, you can use this demo as an example, https://github.com/prashanth-sams/client-api
rake spec