Unit testing typescript RPC and match handlers

I’ve just got Typescript setup instead of using Lua (I’m more familiar with JS). I’d like to unit test my TS files though but having a could of problems.

Problem 1)

Prior to installing any testing libraries such as Jest, I can run tsc no bother:

$ npx tsc

However, as soon as I’ve simply just installed jest, the compiler starts giving me lots of errors:

$ npx tsc
...
error TS2688: Cannot find type definition file for 'api'.
  The file is in the program because:
    Entry point for implicit type library 'api'

error TS2688: Cannot find type definition file for 'bin'.
  The file is in the program because:
    Entry point for implicit type library 'bin'

error TS2688: Cannot find type definition file for 'lib'.
  The file is in the program because:
    Entry point for implicit type library 'lib'

error TS2688: Cannot find type definition file for 'loc'.
  The file is in the program because:
    Entry point for implicit type library 'loc'

error TS2688: Cannot find type definition file for 'rtapi'.
  The file is in the program because:
    Entry point for implicit type library 'rtapi'

  The file is in the program because:
error TS2688: Cannot find type definition file for 'vendor'.

I have skipLibCheck set to true in tsconfig.json file:

{
  "files": [
    "./main.ts",
    "./healthcheck.ts",
    "./match_control.ts",
    "./match_rpc.ts"
  ],
  "compilerOptions": {
    "target": "es5",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "outFile": "./build/index.js",
    "typeRoots": [
      "./node_modules"
    ],
  }
}

Anyway, seems my compiler is broken with this configuration. What am I doing wrong?

Problem 2)

So, even with my compiler broken, I could try and get testing working. I’m installing these libraries

$ npm install jest ts-jest @types/jest --save-dev
$ npm install jest -g

Here is the jest.config.js file:

module.exports = {
  transform: {'^.+\\.ts?$': 'ts-jest'},
  testEnvironment: 'node',
  testRegex: '/tests/.*\\.(test|spec)?\\.(ts|tsx)$',
  moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node']
};

Here is the function being tested (./healthcheck.ts):

function rpcHealthcheck(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, payload: string): string {
    logger.info("healthcheck rpc called");
    return JSON.stringify({success: true});
}

And here is the test:

import { healthcheck } from '../healthcheck';

describe('testing index file', () => {
  test('empty string should result in zero', () => {
    const result = JSON.parse(healthcheck())
    expect(result.success).toBe(true);
  });
});

But when I run the test, I get the following:

$ jest

 FAIL  tests/healthcheck.test.ts
  ● Test suite failed to run

    tests/healthcheck.test.ts:1:29 - error TS2306: File 'C:/Users/marty/Godot/Projects/Tiny Football Network/nakama/healthcheck.ts' is not a module.

    1 import { healthcheck } from '../healthcheck';
                                  ~~~~~~~~~~~~~~~~
    tests/healthcheck.test.ts:3:1 - error TS2582: Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try 
`npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.

    3 describe('testing index file', () => {
      ~~~~~~~~
    tests/healthcheck.test.ts:4:3 - error TS2582: Cannot find name 'test'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.

    4   test('empty string should result in zero', () => {
        ~~~~
    tests/healthcheck.test.ts:6:5 - error TS2304: Cannot find name 'expect'.

    6     expect(result.success).toBe(true);
          ~~~~~~

Test Suites: 1 failed, 1 total
Tests:       0 total
Snapshots:   0 total
Time:        7.064 s
Ran all test suites.

To try and solve the is not a module error, I’ve tried adding export before the healthcheck function, but that just gives me more errors in the editor:

export function rpcHealthcheck(...

Cannot compile modules using option ‘outFile’ unless the ‘–module’ flag is ‘amd’ or ‘system’.ts(6131)

Any help?

Hi @bizt,

The standard typescript configuration that we supply in our documentation does not support more complex use cases such as import statements out of the box and so this may be why you’re seeing these issues when you try to use Jest.

Please have a look at the documentation to configure your TypeScript server using Rollup which will then allow you to more easily make use of third-party packages.

Once you have your project running with Rollup, you can do:

npm i -D jest ts-jest @types/jest

You can then add your jest.config.js file as you already have (make sure the testRegex value is a regex that points to the correct path for your test files) and you should then be able to run Jest without issue.

If you have any further questions please let me know.

Kind regards,
Tom

Thanks Tom, just set things up now

Slight typo into the docs page was giving me an error:

file: build/index.js,

[!] ReferenceError: build is not defined

Ought to have quotes(?):

file: 'build/index.js',

I have jest installed, not disturbing the compiler and I can run a simple test on a simple function so I have something to work with now.

Ideally, I want to test the matchInit and matchLoop functions but I don’t know if it’ll be easy to mock the parameter types e.g. nkruntime.Presence. Do you know if there is an example of this in action?

Otherwise, I’ll just offload as much code as I can to helper functions that don’t depend so much on Nakama types and just test those. So the match handler functions will be pretty lightweight. Still, it would be nice to be able to test those functions and even spy on the parameters. For example, ensure that broadcastMessages function was called on dispatcher when testing matchLoop.

Anyway, thanks for getting me this far.

I’m glad you now have tests working.

We don’t currently have examples of writing unit tests against a match handler with mocked parameters but this is something we will consider for inclusion in our documentation.