How to have k6 not treat http 422 response as a failed request?

I have a group that sends erraneous input requests and is supposed to get http 422 response in return. even the checks(e.g., for status-code) pass[by check, i mean import { check } from 'k6';], but these requests get counted towards “http_req_failed” in the summary of ‘k6 run’? How do I avoid that and reserve “http_req_failed” for requests that are timed out and responses other than 422?

note: I am using 422 specifically because of this SO thread.

Hello, welcome to the forum!

You should be able to use setResponseCallback to have 422 counted as expected.

1 Like

It is not woking for me, I am probably doing it wrong.

here’s how I am adding it, but i still get same “http_req_failed” rate.

import http from 'k6/http';
import { group, check } from 'k6';

export default function () {
      group('improper_input_simple_request', function () {

        const input_payload = JSON.stringify({
            'my': 'input', 'json': 'payload'
        });

        const headers = {
                'Content-Type': 'application/json',
                'Accept': '*/*'
        };
        
        const returned_response = JSON.stringify({
                'my': 'expected', 'json': 'response'
        });
        
        const response = http.post(url_predict, input_payload, {headers});

        http.setResponseCallback(http.expectedStatuses(422));


        check (response, {
                'status is 422': response.status === 422,
                // 'output contains the pre-defined body'
                'output check': response.body === returned_response
        });

      });
};

Close!

At the top of your script, in the init context, declare this:

const only422Callback = http.expectedStatuses(422);

Then, use it like this:

const response = http.post(url_predict, input_payload, {
  headers, 
  responseCallback: only422callback
});

Here’s a complete working example:

import http from 'k6/http';

const only422callback = http.expectedStatuses(422);

export default function () {
  const headers = {
    'Content-Type': 'application/json',
  }
  
  const res = http.get('https://httpstat.us/422', {
    headers,
    responseCallback: only422callback,
  });

  console.log(res.status);
}

In your example, you are setting 422 as the expected response status globally for all requests, but only after you’ve already sent the request and received the response.

1 Like

it doesn’t work. i have tried different combinations.

read the code and result in the comments below.

import http from 'k6/http';
import { group, check } from 'k6';

const only422Callback = http.expectedStatuses(422);

export default function () {
      group('improper_input_simple_request', function () {

        const input_payload = JSON.stringify({
         'my': 'input', 'json': 'payload'
        });

        const json_headers = {
                'Content-Type': 'application/json',
                'Accept': '*/*'
        };
        
        const returned_response = JSON.stringify({
               'my': 'expected', 'json': 'response'
        });
        // try 1: your sugesstion. and this generates following error and also doesn't let other requests in other groups below this be sent.
        // ```
        // ERRO[0001] ReferenceError: only422callback is not defined
        // running at file:///<my-path>/<my-file>.js::115:105(47)
        // default at go.k6.io/k6/js/modules/k6.(*K6).Group-fm (native)
        //   at file:///<my-path>/<my-file>.js::100:6(19)
        //   at native  executor=ramping-vus scenario=default source=stacktrace
        // ```
        // const response = http.post(url_predict, input_payload, {headers: json_headers, responseCallback: only422callback});
        // try 2: same result as above, try 1.
        // const response = http.post(url_predict, input_payload, {headers: json_headers}, {responseCallback: only422callback});
        // try 3: improving on your suggestion, specifying the status code directly.
        // generates no error but doesn't work. i see same % in http_request_failed. and requests in other groups run as they shoud be.
        // const response = http.post(url_predict, input_payload, {headers: json_headers, responseCallback: http.expectedStatuses(422)});
        // try 4: same result as above, try 3.
        // const response = http.post(url_predict, input_payload, {headers: json_headers}, {responseCallback: http.expectedStatuses(422)});
        // try 5: generates following error:
        // ```
        // ERRO[0000] SyntaxError: file:///<my-path>/<my-file>.js: Unexpected token, expected , (128:104)
        //   126 | // > 124 |         const response = http.post(url_predict, input_payload, {headers: json_headers}, responseCallback: only422callback);
        //   127 | //         ```
        // > 128 |         const response = http.post(url_predict, input_payload, {headers: json_headers}, responseCallback: only422callback);
        //       |                                                                                                         ^
        // ```
        // const response = http.post(url_predict, input_payload, {headers: json_headers}, responseCallback: only422callback);
        // try 6: results in same error as above, try 
        // const response = http.post(url_predict, input_payload, {headers: json_headers}, responseCallback: http.expectedStatuses(422));
        // try 7: same result as try 1.
        // const response = http.post(url_predict, input_payload, {headers: json_headers}, only422callback);
        // try 8: same result as try 3.
        // const response = http.post(url_predict, input_payload, {headers: json_headers}, http.expectedStatuses(422));
        // try 9: same result as try 1.
        // const response = http.post(url_predict, input_payload, {headers: json_headers, only422callback});
        // try 10: same result as try 5, but syntax error highligting the period between http and expectedStatuses.
        // const response = http.post(url_predict, input_payload, {headers: json_headers, http.expectedStatuses(422)});


        check (response, {
                'status is 422': response.status === 422,
                // 'output contains the pre-defined body'
                'output check': response.body === returned_response
        });

      });
};

@naveenmarthala you are defining only422Callback with an upper case C and using it with lower case c :wink:

1 Like

thanks @mstoykov, i got the casing wrong. but it still doesn’t work.

same thing happened as specified in my try 3, that is:

// try 1:
const only422Callback = http.expectedStatuses(422);
const response = http.post(url_predict, input_payload, {headers: json_headers, responseCallback: only422Callback});
// try 2:
const response = http.post(url_predict, input_payload, {headers: json_headers, responseCallback: http.expectedStatuses(422)});

this generates no error and all the groups below this one generate expected results, but this doesn’t work. i see same % in http_request_failed.

I got it to work, key difference with my code and yours is a comma after the callback.

yours:
const response = http.post(url_predict, input_payload, {headers: json_headers, responseCallback: only422Callback});
Mine:
const loginResponse = http.post(loginUrl.toString(), loginPayload,{loginParams,responseCallback: only422callback,});

Code Samples:

const only422callback = http.expectedStatuses(422);

const loginResponse = http.post(loginUrl.toString(), loginPayload,{loginParams,responseCallback: only422callback,});
const loginResponseCheck = check(loginResponse, {
    'status is 422': (r) => r.status === 422,                     // contains http 422
    'response body': (r) => r.body.indexOf('userToken') !== -1,   // contains string userToken OK
  });
And the output:
✓ status is 422
✓ response body

     01Login........................: avg=404.785  min=404.785  med=404.785  max=404.785  p(90)=404.785  p(95)=404.785 
     checks.........................: 100.00% ✓ 2        ✗ 0
     data_received..................: 268 B   646 B/s
     data_sent......................: 148 B   357 B/s
     http_req_blocked...............: avg=368µs    min=368µs    med=368µs    max=368µs    p(90)=368µs    p(95)=368µs   
     http_req_connecting............: avg=228µs    min=228µs    med=228µs    max=228µs    p(90)=228µs    p(95)=228µs   
     http_req_duration..............: avg=404.78ms min=404.78ms med=404.78ms max=404.78ms p(90)=404.78ms p(95)=404.78ms
       { expected_response:true }...: avg=404.78ms min=404.78ms med=404.78ms max=404.78ms p(90)=404.78ms p(95)=404.78ms
     http_req_failed................: 0.00%   ✓ 0        ✗ 1
     http_req_receiving.............: avg=1.35ms   min=1.35ms   med=1.35ms   max=1.35ms   p(90)=1.35ms   p(95)=1.35ms  
     http_req_sending...............: avg=225µs    min=225µs    med=225µs    max=225µs    p(90)=225µs    p(95)=225µs   
     http_req_tls_handshaking.......: avg=0s       min=0s       med=0s       max=0s       p(90)=0s       p(95)=0s      
     http_req_waiting...............: avg=403.2ms  min=403.2ms  med=403.2ms  max=403.2ms  p(90)=403.2ms  p(95)=403.2ms 
     http_reqs......................: 1       2.408472/s
     iteration_duration.............: avg=408.4ms  min=408.4ms  med=408.4ms  max=408.4ms  p(90)=408.4ms  p(95)=408.4ms 
     iterations.....................: 1       2.408472/s
1 Like

@naveenmarthala

import http from 'k6/http';
import { check } from 'k6';

const only422Callback = http.expectedStatuses(422);

export default function() {
  const json_headers = { "someheader": "value" }
  const response = http.post("https://httpbin.test.k6.io/status/422", "something", { headers: json_headers, responseCallback: only422Callback});
  check(response, {
    'status is 422': response.status === 422,
  });

};

definitely works for me for all kind of versions of k6. The output shows

     ✓ status is 422

     checks.........................: 100.00% ✓ 1        ✗ 0
     data_received..................: 5.7 kB  11 kB/s
     data_sent......................: 585 B   1.1 kB/s
     http_req_blocked...............: avg=416.66ms min=416.66ms med=416.66ms max=416.66ms p(90)=416.66ms p(95)=416.66ms
     http_req_connecting............: avg=117.26ms min=117.26ms med=117.26ms max=117.26ms p(90)=117.26ms p(95)=117.26ms
     http_req_duration..............: avg=120.27ms min=120.27ms med=120.27ms max=120.27ms p(90)=120.27ms p(95)=120.27ms
       { expected_response:true }...: avg=120.27ms min=120.27ms med=120.27ms max=120.27ms p(90)=120.27ms p(95)=120.27ms
     http_req_failed................: 0.00%   ✓ 0        ✗ 1
     http_req_receiving.............: avg=140.52µs min=140.52µs med=140.52µs max=140.52µs p(90)=140.52µs p(95)=140.52µs
     http_req_sending...............: avg=135.28µs min=135.28µs med=135.28µs max=135.28µs p(90)=135.28µs p(95)=135.28µs
     http_req_tls_handshaking.......: avg=266.56ms min=266.56ms med=266.56ms max=266.56ms p(90)=266.56ms p(95)=266.56ms
     http_req_waiting...............: avg=120ms    min=120ms    med=120ms    max=120ms    p(90)=120ms    p(95)=120ms
     http_reqs......................: 1       1.851053/s
     iteration_duration.............: avg=537.48ms min=537.48ms med=537.48ms max=537.48ms p(90)=537.48ms p(95)=537.48ms
     iterations.....................: 1       1.851053/s

which means that:

  1. ✓ status is 422 all checks with that name passed os the status code was 422
  2. http_req_failed................: 0.00% ✓ 0 ✗ 1 0% means that there were 0% failed with 0 failed and 1 not failed. Unfortunately we noticed later that this double negative isn’t exactly great for users understanding of what is happening.

The URL used will return w/e status you give it as the last part of the path and you can test around it to make certain that you understand how this works. From the looks of it either:

  1. you are making some small mistake making the script
  2. you are having a problem with reading the output due to the unfortunate UX issue we have :frowning:

@BobRuub as far as I can see your change shouldn’t do anything although this is javascript so I would expect that depending on what login_params is … a comma might make a difference :person_shrugging: . If you want you can provide and httpbin.test.k6.io based full example so I can dig further. I at least can’t reproduce it breaking without a comma :person_shrugging:

1 Like

Mine also works without the comma so clearly not an important difference.

It’s my usual approach to debugging, identify the differences and eliminate them, if it makes no difference then move on.

1 Like

Huge Thanks for your effort and time, @mstoykov, @BobRuub and @ Tom