0

So my code looks like this:

import FsAsyncFactory from '../fs/FsAsyncFactory'
import ObjectHelper from '../utils/ObjectHelper'
import Config from './Config'

export class ConfigHelper {

    // this is the function under test
    public static async primeCacheObj(): Promise<void> {

        const configFileObj: any = await ConfigHelper.getConfigFileObj()

    }

    private static configCacheObj: Config = null

    private static async getConfigFileObj(): Promise<any> {
        const fsAsync: any = FsAsyncFactory.getFsAsync()
        const fileContents: string = await fsAsync.readFileAsync('./config/hubnodeConfig.json')

        return JSON.parse(fileContents)
    }

}

export default ConfigHelper

What's the best way to allow my unit testing code to stop it from actually hitting the disk?

Should I be using something like InversifyJS to allow dependency injection? I'd have an init script that would setup the "defaults" for when the app is running normally then "override" those defaults in the tests?

Force Hero
  • 2,674
  • 3
  • 19
  • 35

1 Answers1

1

In JavaScript, traditionally people have been using monkey patching to write unit tests.

I personally think that monkey patching is a bad practice and leads to unmaintainable code. I prefer dependency injection and that is why I created InversifyJS.

You can use InversifyJS to override bindings on unit tests.

This means that ConfigHelper will get an instance of FsAsync via dependency injection.

@injectable()
export class ConfigHelper {

    @inject("FsAsync") private readonly _fsAsync: FsAsync;

    // this is the function under test
    public static async primeCacheObj(): Promise<void> {

        const configFileObj: any = await ConfigHelper.getConfigFileObj()

    }

    private static configCacheObj: Config = null

    private static async getConfigFileObj(): Promise<any> {
        const json = await this_fsAsync.readFileAsync('./xxx.json')
        return JSON.parse(json)
    }

}

During the unit test you can replace the FsAsync type binding:

container.bind<FsAsync>("FsAsync")
         .toDynamicValue(() => FsAsyncFactory.getFsAsync());

To inject a mock:

container.rebind<FsAsync>("FsAsync")
         .toDynamicValue(() => ({
             readFileAsync: (path) => {
                 return Promise.resolve("{ hardCodedJson: "happy days!" }");
             }
         }));
Remo H. Jansen
  • 23,172
  • 11
  • 70
  • 93
  • 1
    Hey! Thanks for responding! I tried your DI solution and couldn't get it working - I will try again when I'm at a machine. It looks the business by the way so I'm excited to see it work! Cheers. – Force Hero Dec 16 '17 at 08:25
  • Hmm.. I followed your suggestion but I get a TS error on the `@inject` which says `Unable to resolve signature of property decorator when called as an expression.` I'm importing `import 'reflect-metadata'` in my index.ts file and this is the import on the ConfigHelper `import { inject, injectable } from 'inversify'` I used this to install inversify: `npm install inversify reflect-metadata --save` This is the project: https://github.com/adamdry/node-di-mocha-test Any ideas? Thanks! – Force Hero Dec 16 '17 at 10:35
  • Hi, sorry I wrote the code here in SO but I didn't compile it or run it. I think the problem is that I typed `@inject` instead of `@inject("FsAsync")`. As I said I have not compiled the code so maybe there are more errors but you should be able to figure out what went wrong if you check the project docs: https://github.com/inversify/InversifyJS/blob/master/README.md#the-basics – Remo H. Jansen Dec 16 '17 at 13:23
  • 1
    Thank you good sir! I'll give it a crack when I'm in front of the machine. Appreciate the help. – Force Hero Dec 16 '17 at 13:32