@@ -3,7 +3,7 @@ title: 'API CRUD Operations'
33excerpt : ' This example covers the usage of k6 to test a REST API CRUD operations.'
44---
55
6- The example showcases the testing of CRUD operations on a REST API.
6+ The examples showcase the testing of CRUD operations on a REST API.
77
88CRUD refers to the basic operations in a database: Create, Read, Update, and Delete. We can map these operations to HTTP methods in REST APIs:
99
@@ -12,6 +12,8 @@ CRUD refers to the basic operations in a database: Create, Read, Update, and Del
1212- _ Update_ : HTTP ` PUT ` or ` PATCH ` to change an existing resource.
1313- _ Delete_ : HTTP ` DELETE ` to remove a resource.
1414
15+ You'll find two test examples: the first one uses the core k6 APIs (` k6/http ` and ` checks ` ), and the second showcases more recent APIs (` httpx ` and ` k6chaijs ` ).
16+
1517## Test steps
1618
1719In the [ setup() stage] ( /using-k6/test-lifecycle/#setup-and-teardown-stages ) we create a user for the [ k6 HTTP REST API] ( https://test-api.k6.io/ ) . We then retrieve and return a bearer token to authenticate the next CRUD requests.
@@ -23,12 +25,11 @@ The steps implemented in the [VU stage](/using-k6/test-lifecycle/#the-vu-stage)
23253 . _ Update_ the name of the "croc" and _ read_ the "croc" to confirm the update operation.
24264 . _ Delete_ the "croc" resource.
2527
26- <CodeGroup labels={[ "api-crud-operations.js"] } lineNumbers={[ true] }>
28+ <CodeGroup labels={[ "api-crud-operations-k6-core-apis .js"] } lineNumbers={[ true] }>
2729
2830``` javascript
29- import { describe , expect } from ' https://jslib.k6.io/k6chaijs/4.3.4.3/index.js' ;
30- import { Httpx } from ' https://jslib.k6.io/httpx/0.1.0/index.js' ;
31- import { randomIntBetween , randomItem } from " https://jslib.k6.io/k6-utils/1.2.0/index.js" ;
31+ import http from ' k6/http' ;
32+ import { check , group , fail } from ' k6' ;
3233
3334export const options = {
3435 thresholds: {
@@ -43,11 +44,6 @@ export const options = {
4344 iterations: 1
4445};
4546
46- const USERNAME = ` user${ randomIntBetween (1 , 100000 )} @example.com` ; // Set your own email;
47- const PASSWORD = ' superCroc2019' ;
48-
49- const session = new Httpx ({ baseURL: ' https://test-api.k6.io' });
50-
5147// Create a random string of given length
5248function randomString (length , charset = ' ' ) {
5349 if (! charset) charset = ' abcdefghijklmnopqrstuvwxyz' ;
@@ -56,7 +52,131 @@ function randomString(length, charset = '') {
5652 return res;
5753}
5854
59- // Authenticate user and retrieve authentication token for the API requests
55+ const USERNAME = ` ${ randomString (10 )} @example.com` ; // Set your own email or `${randomString(10)}@example.com`;
56+ const PASSWORD = ' superCroc2019' ;
57+
58+ const BASE_URL = ' https://test-api.k6.io' ;
59+
60+ // Register a new user and retrieve authentication token for subsequent API requests
61+ export function setup () {
62+ const res = http .post (` ${ BASE_URL } /user/register/` , {
63+ first_name: ' Crocodile' ,
64+ last_name: ' Owner' ,
65+ username: USERNAME ,
66+ password: PASSWORD ,
67+ });
68+
69+ check (res, { ' created user ' : (r ) => r .status === 201 });
70+
71+ const loginRes = http .post (` ${ BASE_URL } /auth/token/login/` , {
72+ username: USERNAME ,
73+ password: PASSWORD ,
74+ });
75+
76+ const authToken = loginRes .json (' access' );
77+ check (authToken, { ' logged in successfully ' : () => authToken !== ' ' });
78+
79+ return authToken;
80+ }
81+
82+ export default (authToken ) => {
83+ // set the authorization header on the session for the subsequent requests
84+ const requestConfigWithTag = (tag ) => ({
85+ headers: {
86+ Authorization: ` Bearer ${ authToken} ` ,
87+ },
88+ tags: Object .assign (
89+ {},
90+ {
91+ name: ' PrivateCrocs' ,
92+ },
93+ tag
94+ ),
95+ });
96+
97+ let URL = ` ${ BASE_URL } /my/crocodiles/` ;
98+
99+ group (' 01. Create a new crocodile' , () => {
100+ const payload = {
101+ name: ` Name ${ randomString (10 )} ` ,
102+ sex: ' F' ,
103+ date_of_birth: ' 2023-05-11' ,
104+ };
105+
106+ const res = http .post (URL , payload, requestConfigWithTag ({ name: ' Create' }));
107+
108+ if (check (res, { ' Croc created correctly ' : (r ) => r .status === 201 })) {
109+ URL = ` ${ URL }${ res .json (' id' )} /` ;
110+ } else {
111+ console .log (` Unable to create a Croc ${ res .status } ${ res .body } ` );
112+ return ;
113+ }
114+ });
115+
116+ group (' 02. Fetch private crocs' , () => {
117+ const res = http .get (` ${ BASE_URL } /my/crocodiles/` , requestConfigWithTag ({ name: ' Fetch' }));
118+ check (res, { ' retrieved crocs status ' : (r ) => r .status === 200 });
119+ check (res .json (), { ' retrieved crocs list ' : (r ) => r .length > 0 });
120+ });
121+
122+ group (' 03. Update the croc' , () => {
123+ const payload = { name: ' New name' };
124+ const res = http .patch (URL , payload, requestConfigWithTag ({ name: ' Update' }));
125+ const isSuccessfulUpdate = check (res, {
126+ ' Update worked ' : () => res .status === 200 ,
127+ ' Updated name is correct ' : () => res .json (' name' ) === ' New name' ,
128+ });
129+
130+ if (! isSuccessfulUpdate) {
131+ console .log (` Unable to update the croc ${ res .status } ${ res .body } ` );
132+ return ;
133+ }
134+ });
135+
136+ group (' 04. Delete the croc' , () => {
137+ const delRes = http .del (URL , null , requestConfigWithTag ({ name: ' Delete' }));
138+
139+ const isSuccessfulDelete = check (null , {
140+ ' Croc was deleted correctly ' : () => delRes .status === 204 ,
141+ });
142+
143+ if (! isSuccessfulDelete) {
144+ console .log (` Croc was not deleted properly` );
145+ return ;
146+ }
147+ });
148+
149+ };
150+ ```
151+
152+ </CodeGroup >
153+
154+ <CodeGroup labels={[ "api-crud-operations-k6-new-apis.js"] } lineNumbers={[ true] }>
155+
156+ ``` javascript
157+ import { describe , expect } from ' https://jslib.k6.io/k6chaijs/4.3.4.3/index.js' ;
158+ import { Httpx } from ' https://jslib.k6.io/httpx/0.1.0/index.js' ;
159+ import { randomIntBetween , randomItem , randomString } from " https://jslib.k6.io/k6-utils/1.2.0/index.js" ;
160+
161+ export const options = {
162+ thresholds: {
163+ checks: [{
164+ threshold: ' rate == 1.00' , abortOnFail: true ,
165+ }],
166+ ' http_req_duration' : [' p(90)<25000' , ' p(95)<30000' ],
167+ ' http_req_duration{name:Create}' : [' avg<15000' , ' max<25000' ],
168+ },
169+ // for the example, let's run only 1 VU with 1 iteration
170+ vus: 1 ,
171+ iterations: 1 ,
172+ };
173+
174+ const USERNAME = ` user${ randomIntBetween (1 , 100000 )} @example.com` ; // Set your own email;
175+ const PASSWORD = ' superCroc2019' ;
176+
177+ const session = new Httpx ({ baseURL: ' https://test-api.k6.io' });
178+
179+ // Register a new user and retrieve authentication token for subsequent API requests
60180export function setup () {
61181
62182 let authToken = null ;
@@ -70,7 +190,7 @@ export function setup() {
70190 });
71191
72192 expect (resp .status , ' User create status' ).to .equal (201 );
73- expect (resp).to .have .validJsonBody ();
193+ expect (resp, ' User create valid json response ' ).to .have .validJsonBody ();
74194 });
75195
76196 describe (` setup - Authenticate the new user ${ USERNAME } ` , () => {
@@ -79,18 +199,18 @@ export function setup() {
79199 password: PASSWORD
80200 });
81201
82- expect (resp .status , ' Auth status' ).to .equal (200 );
83- expect (resp).to .have .validJsonBody ();
202+ expect (resp .status , ' Authenticate status' ).to .equal (200 );
203+ expect (resp, ' Authenticate valid json response ' ).to .have .validJsonBody ();
84204 authToken = resp .json (' access' );
85- expect (authToken, ' auth token' ).to .not .be .null ;
205+ expect (authToken, ' Authentication token ' , ' auth token' ).to .not .be .null ;
86206 });
87207
88208 return authToken;
89209}
90210
91211export default function (authToken ) {
92212
93- // set the authorization header on the session for the subsequent requests.
213+ // set the authorization header on the session for the subsequent requests
94214 session .addHeader (' Authorization' , ` Bearer ${ authToken} ` );
95215
96216 describe (' 01. Create a new crocodile' , (t ) => {
@@ -104,7 +224,7 @@ export default function (authToken) {
104224 const resp = session .post (` /my/crocodiles/` , payload);
105225
106226 expect (resp .status , ' Croc creation status' ).to .equal (201 );
107- expect (resp).to .have .validJsonBody ();
227+ expect (resp, ' Croc creation valid json response ' ).to .have .validJsonBody ();
108228
109229 session .newCrocId = resp .json (' id' );
110230 })
@@ -115,8 +235,8 @@ export default function (authToken) {
115235 const resp = session .get (' /my/crocodiles/' );
116236
117237 expect (resp .status , ' Fetch croc status' ).to .equal (200 );
118- expect (resp).to .have .validJsonBody ();
119- expect (resp .json ().length , ' number of crocs' ).to .be .above (0 );
238+ expect (resp, ' Fetch croc valid json response ' ).to .have .validJsonBody ();
239+ expect (resp .json ().length , ' Number of crocs' ).to .be .above (0 );
120240 })
121241
122242 describe (' 03. Update the croc' , (t ) => {
@@ -127,15 +247,15 @@ export default function (authToken) {
127247 const resp = session .patch (` /my/crocodiles/${ session .newCrocId } /` , payload);
128248
129249 expect (resp .status , ' Croc patch status' ).to .equal (200 );
130- expect (resp).to .have .validJsonBody ();
131- expect (resp .json (' name' )).to .equal (payload .name );
250+ expect (resp, ' Fetch croc valid json response ' ).to .have .validJsonBody ();
251+ expect (resp .json (' name' ), ' Croc name ' ).to .equal (payload .name );
132252
133253 // read "croc" again to verify the update worked
134254 const resp1 = session .get (` /my/crocodiles/${ session .newCrocId } /` );
135255
136256 expect (resp1 .status , ' Croc fetch status' ).to .equal (200 );
137- expect (resp1).to .have .validJsonBody ();
138- expect (resp1 .json (' name' )).to .equal (payload .name );
257+ expect (resp1, ' Fetch croc valid json response ' ).to .have .validJsonBody ();
258+ expect (resp1 .json (' name' ), ' Croc name ' ).to .equal (payload .name );
139259
140260 })
141261
0 commit comments