Exported method from a class is not available in the test & teardown function

Hello there,

TLDR: I have a few test cases that share a code, and there are some issues accessing exported methods.

There are some tests that access the Keycloak App through the API (could be any other application), so I wrote a simple client that wraps some common methods and it’s available as a class in a standalone file keycloak.js:

export class Keycloak {
    /**
     * Creates a keycloak client from the server URL and params.
     *
     * Currently, accepted params:
     * - offlineTokens (boolean): request offline tokens instead of regular refresh tokens
     * @param {String} keycloakURL the keycloak server URL
     * @param {String} adminUsername Master real admin user username
     * @param {String} adminPassword Master real admin user password
     */
    constructor(keycloakURL, adminUsername, adminPassword) {
        this.keycloakURL = keycloakURL;
        this.username = adminUsername;
        this.password = adminPassword;
        this.populatedRealms = [];
    }
   
    // Some other methods
    // ...

    dropTestAssets() {
        let accessToken = this.getMasterAccessToken();

        for (let i = 0; i < this.populatedRealms.length; i++) {
            let realmDeletionResponse = http.delete(
                this.customEndpoint(`/auth/admin/realms/${this.populatedRealms[i].id}`),
                {},
                {
                    headers: {
                        "Content-Type": "application/json",
                        "Authorization": `Bearer ${accessToken}`
                    }
                });

            check(realmDeletionResponse, {
                'Was realm deleted?': (resp) => resp.status === 201,
            });
        }

        this.populatedRealms = [];
    }
}

My idea is to import this client every time I would write a new test, so I can reuse its methods:

import {check} from 'k6';
import {
    getTestConfig,
    Keycloak,
} from "./keycloak.js";

const config = getTestConfig();

export let options = {
    insecureSkipTLSVerify: true,
    summaryTimeUnit: 'ms',
    setupTimeout: '5m',
    teardownTimeout: '5m',
    thresholds: {
        'http_req_duration{status:200}': ['max>=0'],
        'http_req_duration{status:201}': ['max>=0'],
        'http_req_duration{status:400}': ['max>=0'],
        'http_req_duration{status:401}': ['max>=0'],
        'http_req_duration{status:403}': ['max>=0'],
        'http_req_duration{status:429}': ['max>=0'],
        'http_req_duration{status:500}': ['max>=0'],
        'http_req_duration{status:502}': ['max>=0'],
    },
    summaryTrendStats: ['avg', 'min', 'med', 'max', 'p(90)', 'p(95)', 'p(99)', 'p(99.99)', 'count'],
};

export function setup() {
    let keycloak = new Keycloak(config.keycloakURL, config.username, config.password);

    check(keycloak, {
        'Built Keycloak client': () => (keycloak != null),
    })

    let realmsNumber = 1;
    let clientsPerRealm = 10;
    let usersPerRealm = 100;

    let realms = keycloak.populateTestAssets(realmsNumber, clientsPerRealm, usersPerRealm);

    return {
        keycloakClient: keycloak,
        loadedRealms: realms,
    }
}

export default function (toolbox) {
    toolbox.keycloakClient.dropTestAssets() // <--- Fails there
}

export function teardown(toolbox) {
    check(toolbox.keycloakClient, {
        'Is client available?': () => (toolbox.keycloakClient != null),
    })

    toolbox.keycloakClient.dropTestAssets() // <--- Fails there
}

But once I initialized the client in the setup() method and returned it to be available in the execution in the teardown function I’m always getting an error:

ERRO[0009] TypeError: Object has no member 'dropTestAssets'
default ✓ [======================================] 1 VUs  1s
	at teardown (file:///Users/some/path/is/hidden/keycloak_dummy_k6.js:58:4(15))
	at native  hint="script exception"

Feels like it’s not able to access the method (am I missing a keyword public) or do I need to implicitly cast the object to the Keycloak class?

I would appreciate any kind of feedback, thanks!

Hi @SanchezU, welcome to the community forum :tada:

setup, teardown and each VU’s default function all run in separate VUs which are in themselves separate js VMs.

In order to move the return of setup from setup to everything else it gets marshalled to JSON, and unmarshalled back for each one. Given that a method is a function and there is no (known to me) marshaller to JSON that will marshal functions, it just gets skipped. Which is actually inline with what JSON.stringify does - which is also why we went with it ;).

This is explained (among other drawbacks) here after the two example, but I understand how it can be confusing especially as in this case the class goes to an object.

I have opened an issue as I haven’t found any, but this might not get prioritized, so if you want you can have a shot at it :wink:

As a workaround, I guess you can keep transporting the data with the setupData but have plain functions that just work on it. Or maybe reconstruct the class instance when needed.

Hope this helps you.

1 Like

Hello @mstoykov, thanks for the great explanation!
As a workaround in my case, I would try to initialize an object of the class outside the default/setup/teardown functions so, in theory, should keep data types.