Unit testing k6 scripts

Hello,

I have a project where I’m housing all my k6 test scripts. I want to add unit testing (Jest) and lint (eslint) to the project. However, when I try to run lint or tests for any files containing k6 imports, I run into errors resolving the k6 libraries:

Cannot find module 'k6/http' from 'test.js'
> 1 | import http from "k6/http"

I’ve tried adding the k6 and @types/k6 packages via Yarn but neither of these helped resolve the issue above. Is it possible to vendor in the k6 library for testing purposes? I was wondering what the recommended process is for setting up unit testing for k6 scripts?

Hi @sms1070,

as was explained in your previous thread the “k6/*” imports are part of k6 they are not a JS library and k6 is not nodejs based.

This would mean that IMO running Jest tests will be really hard … You will either

  1. Need to run it only on parts that don’t use k6/* imports or figure out how provide and adequate replacement. This is probably way too much work and will have a lot of cases where your code works in Jest, but when you run it in k6 it breaks because you haven’t emulated the k6 behavior exactly.
  2. Try to run the test with k6 directly by making a script that instead of actually performance testing is running test … I don’t think Jest will bend enough for this to work though :smiley: and will definitely require a lot of JS knowledge and code.

This all bares the question why do you want to do that … I don’t think anyone else has ever tried to unit test their performance tests … I guess it could be useful in some very big tests or such with very strange logic, but still the proposed running of a single iterations seems to be sufficient in a lot of cases.

There’s quite a few reasons I can see why you would want to unit test your scripts:

  • Test common utility functions that may deal with organization-specific requirements ie., generating special headers, handle authorization etc. This may be easier to test since we can abstract it away in a separate set of files and restrict the use of any “k6/*” imports (not ideal, but doable).
  • Ensure that your performance script is syntactically correct and has the expected semantic side-effects ie., the correct headers were passed, dynamic endpoint generation worked as expected, an expected number of requests were made in a single function call etc. Having unit testing not only does a point-in-time check on these requirements, but it also ensures that we don’t have regressions on these in future iterations of the scripts.

I am actually quite surprised that this use case isn’t supported or hasn’t come up before. I wonder if there’s some mocking of k6 libraries we can do for our tests to make this process easier for us.

I believe the most sane way to do this would be to have a separate test script that does this validation, and run that with one VU for one iteration. If you bundle this with the expect package, you’ll pretty much be good to go at that point.

Check out @mstojkov’s example repo for bundling and transpiling with webpack and babel for more details on how to set up the actual bundling.

For instance:

// ./some.helper.js

export function functionThatHelps() {
  return 'help';
}

// ./main.test.js

import { functionThatHelps} from './some.helper';

export default function () {
  // my actual test
  functionThatHelps();  
}

// ./unit.test.js

import { expect } from 'expect';
import { functionThatHelps } from './some.helper';

export default function () {
  expect(functionThatHelps()).toEqual('help');
}

Now, you’d be able to bundle and run main.test.js to run your actual k6 tests, and expect.test.js to run unit tests on your performance test logic. You’d also still have access to the built-in k6 packages as you will run the unit test with k6 as the test runner.

With that said, this code is written directly in the editor of the community forum, and is only meant to illustrate a pattern (or possible approach if you prefer) rather than being a working example. Hopefully, it will at least serve as a nudge in a possibly right direction. :sweat_smile:

Oh, and I’d be very interested in learning more about what you’re working on. If you’d be open to chat a bit about it, feel free to hit me up on simme@k6.io or on the k6 community slack.

I completely agree that Unit Testing is needed, as creating bespoke code to meet the needs of whatever app you’re testing is one of k6’s USPs!

I’ve added Jest to Unit Test my ‘helper’ code, but have run into the same problem described above if I use any k6 functionality in a function (e.g. ‘check’ or ‘open’ or even ‘http’).

I haven’t got around to it yet (and I’ve left it as a code smell!) but I was just planning on mocking out the k6 imports. Which I should be doing for Unit Testing anyway.

I can’t see why that wouldn’t work? If I have time (when work calms down), I’ll add it to my example repo.

1 Like

That would likely work. Good idea!

@sstratton - I am facing the same problem, and I am also looking into a Jest mocking solution.
Have you been able to get it working?

No, unfortunately, I haven’t had a chance to give it a go (too busy!). Have you had any luck?

WIP, but it seems pretty straight forward using the manual mocks and creating a mock module <rootDir>/__mocks__/k6.js.

@sstratton - I’ve created a gist with a dummy example: https://gist.github.com/edno/0f17bb4339e1f83ee1eddfed3e882bc7

3 Likes

Nice work! Thanks. Will add it the typescript example also when I get a chance.

Maybe its too late answer, but my approach is a bit different on testing k6 tests: lets use k6 for testing k6 scripts, but mock SUT in k6 script.
The xk6-mock extension is more or less fit for this:

I hope it helps you…

1 Like

In jest you can mock a virtual module like this

import { uuidv4 } from 'https://jslib.k6.io/k6-utils/1.4.0/index.js';

jest.mock('https://jslib.k6.io/k6-utils/1.4.0/index.js', () => ({
  uuidv4: jest.fn()
}), {virtual: true});

const mockedUuidv4 = <jest.Mock>uuidv4;

describe('test something that use uuidv4', () => {
   beforeEach(() => {
    mockedUuidv4.mockReturnValue('i-am-root')
  });

  it('should return something with random uuid', () => {
     expect(uuidv4()).toEqual('i-am-root')
  })
})
1 Like