Skip to content

Applications

A Hitchy-based application usually focuses on exposing an API over HTTP. For unit testing this API the application must be started and stopped before and after running any test suite.

Assuming there is a Hitchy-based project in folder

/path/to/my/app

Its tests may reside in

/path/to/my/app/test

One of the files in that folder could be named example.spec.js. The following code is an example for a simple unit test to be run with mocha:

javascript
import { describe, before, after, it } from "mocha";
import SDT from "@hitchy/server-dev-tools";
import Core from "@hitchy/core/sdt.js";
import "should";

const Test = await SDT( Core );
// Core may be omitted as some existing core's SDT API is used as fallback
// const Test = await SDT();

describe( "A controller of my app", () => {
	const ctx = {};

	before( Test.before( ctx, { options: { ... } } ) );
	after( Test.after( ctx ) );

	it( "properly responds to incoming query", async() => {
		const response = await ctx.post( "/some/url", {
			prop: "value",
		} );

        response.statusCode.should.be.equal( 200 );
	} );
} );
javascript
const { describe, before, after, it } = require( "mocha" );
const SDT = require( "@hitchy/server-dev-tools" );
require( "should" );

describe( "A controller of my app", () => {
    const ctx = {};
    
    before( SDT.before( ctx, { options: { ... } } ) );
    after( SDT.after( ctx ) );
    
    it( "properly responds to incoming query", () => {
        return ctx.post( "/some/url", {
            prop: "value",
        } )
            .then( response => {
                response.statusCode.should.be.equal( 200 );
            } );
    } );
} );

In this example, root folder of project also containing this test file is picked as Hitchy project folder.

Plugins

Testing a plugin is slightly different from testing an application:

  • A plugin is not bound to a particular project folder. Instead, you need to provide a fake project which claims to depend on the plugin to be tested.

  • Different applications might use the same plugin for different use cases. The plugin might behave differently depending on its including application. Thus, proper unit testing requires different projects to test the plugin with. Either project must be faked most probably.

  • The project containing a test suite is implementing the plugin, only. It gets integrated with an application as a dependency residing in that application's node_modules/ folder by design. Due to faking multiple project folders, the plugin gets injected explicitly every time.

Assuming you are developing a plugin in folder

/path/to/my/plugin

There may be tests in

/path/to/my/plugin/test/scripts

Fake project folders could be implemented in

/path/to/my/plugin/test/projects

just like

/path/to/my/plugin/test/projects/candidate-a

A resulting test script /path/to/my/plugin/test/scripts/example.spec.js should look like this:

javascript
import Path from "node:path";

import { describe, before, after, it } from "mocha";
import SDT from "@hitchy/server-dev-tools";
import "should";

const Test = await SDT();

describe( "An application relying on my plugin", () => {
    const ctx = {};
    
    after( Test.after( ctx ) );
    before( Test.before( ctx, {
        pluginTestFolder: Path.join( __dirname, "../.." ),
        projectFolder: Path.join( __dirname, "../project/candidate-a" ),
        options: { ... }
    } ) );
    
    it( "handles request properly", async() => {
        const response = await ctx.post( "/some/url", {
            prop: "value",
        } );

        response.statusCode.should.be.equal( 200 );
    } );
} );
javascript
const Path = require( "node:path" );

const { describe, before, after, it } = require( "mocha" );
const SDT = require( "@hitchy/server-dev-tools" );
require( "should" );

describe( "An application relying on my plugin", () => {
    const ctx = {};
    
    after( SDT.after( ctx ) );
    before( SDT.before( ctx, {
        pluginTestFolder: Path.join( __dirname, "../.." ),
        projectFolder: Path.join( __dirname, "../project/candidate-a" ),
        options: { ... }
    } ) );
    
    it( "handles request properly", () => {
        return ctx.post( "/some/url", {
            prop: "value",
        } )
        .then( response => {
            response.statusCode.should.be.equal( 200 );
        } );
    } );
} );

This example is different from the previous one for testing application in SDT configuration provided:

  • projectFolder is selecting a fake project folder instead of current project's root folder.

  • pluginFolder is picking root folder of plugin's project this test is a part of. This way Hitchy will discover the plugin on running in context of fake application in fake project folder.

Describing Project Folders

Due to relying on existing fake project folders on disk, implementing unit tests for a plugin may be hard to manage. That's why SDT supports self-contained unit tests by describing the desired project folder's content to be written to disk temporarily:

javascript
import { describe, before, after, it } from "mocha";
import SDT from "@hitchy/server-dev-tools";
import "should";

const Test = await SDT();

describe( "An application relying on my plugin", () => {
    const ctx = {};
    
    after( Test.after( ctx ) );
    before( Test.before( ctx, {
        pluginFolder: "../..",
        files: {
            "config/routes.cjs": `exports.routes = { "/foo": ( _, res ) => res.send( "Hello!" ) };`,
        }, 
        options: { ... }
    } ) );
    
    
    it( "handles request properly", async() => {
        const response = await ctx.get( "/foo" );

        response.body.toString( "utf8" ).should.be.equal( "Hello!" );
    } );
} );
javascript
const { describe, before, after, it } = require( "mocha" );
const SDT = require( "@hitchy/server-dev-tools" );
require( "should" );

describe( "An application relying on my plugin", () => {
    const ctx = {};
    
    before( SDT.before( ctx, {
        pluginFolder: "../..",
        files: {
            "config/routes.js": `exports.routes = { "/foo": ( _, res ) => res.send( "Hello!" ) };`,
        }, 
        options: { ... }
    } ) );
    
    after( SDT.after( ctx ) );
    
    it( "handles request properly", () => {
        return ctx.get( "/foo" )
            .then( response => {
                response.body.toString( "utf8" ).should.be.equal( "Hello!" );
            } );
    } );
} );

In this example configuration files is used to provide temporary content for a file to be injected into some temporary project folder. Of course, multiple files may be provided that way.

Combining those two approaches helps with eliminating redundancies. An existing project folder on disk could serve as a common template for several test cases. Either test suite is describing content of files differing from that template, only. SDT will copy the selected project folder to temporary location and adjust files according to provided file descriptions afterwards.

Testing without mocha

SDT does not rely on mocha for running your tests. It works just fine without any particular test runner as demonstrated in this example:

javascript
import Assert from "node:assert";
import SDT from "@hitchy/server-dev-tools";

const { before, after } = await SDT();
const ctx = {};

before( ctx, { plugin: true } )()
    .then( () => ctx.put( "/exposed/route/of/hitchy?foo=bar", { 
        prop: "some info",
    } ) )
    .then( response => {
        Assert.strictEqual( response.statusCode, 200, "HTTP status code must be 200" );
    } )
    .finally( after( ctx ) );
javascript
const Assert = require( "node:assert" );

const { before, after } = require( "@hitchy/server-dev-tools" );
const ctx = {};

before( ctx, { plugin: true } )()
    .then( () => ctx.put( "/exposed/route/of/hitchy?foo=bar", { 
        prop: "some info",
    } ) )
    .then( response => {
        Assert.strictEqual( response.statusCode, 200, "HTTP status code must be 200" );
    } )
    .finally( after( ctx ) );

However, using test runners like mocha is suggested e.g. for conveniently performing multiple tests in scope of a single run of Hitchy.