MortenHoustonLudvigsen / KarmaTestAdapter

A Visual Studio test explorer adapter for Karma
MIT License
20 stars 14 forks source link

A Visual Studio test explorer adapter for Karma

This extension integrates Karma - Spectacular Test Runner for JavaScript with the test explorer in Visual Studio 2013 and Visual Studio 2015 Preview / CTP.

This extension is built using JsTestAdapter.

This test adapter runs tests in browsers using Karma. If you need to run tests in Node.js you might be interested in the Jasmine Node Test Adapter.

Features

Features specific to Jasmine tests

Prerequisites

Installation

Download and install from the Visual Studio Gallery here: Karma Test Adapter

Configuration

Set up Karma normally (as described here: Installation and here: Configuration).

Start testing!

If you want Visual Studio to work differently from how Karma is configured (if you for example only want to run PhantomJS from VS), you can create a JSON settings file called KarmaTestAdapter.json.

Example:

{
    "$schema": "http://MortenHoustonLudvigsen.github.io/KarmaTestAdapter/KarmaTestAdapter.schema.json",
    "KarmaConfigFile": "karma.conf.test.js",
    "Name": "My tests",
    "Extensions": "./Extensions",
    "Traits": [ "Karma", { "Name": "Foo", "Value": "Bar" } ],
    "Disabled": false,
    "LogToFile": true,
    "LogDirectory": "TestResults/Karma",
    "config": {
        "browsers": [
            "PhantomJS"
        ]
    }
}

These are the possible properties (all properties are optional):

KarmaTestAdapter.json must be encoded in one of the following encodings:

Extensions

To customize generation of fully qualified names, display names and traits for each test it is now possible to supply a node module with extensions. An extensions module can implement any or all of these functions:

The following module implemented in typescript implements the default functions:

interface Spec {
    description: string;
    suite: string[];
    traits: Trait[];
}

interface Server {
    testContainerName: string;
}

interface Trait {
    name: string;
    value: string;
}

export function getDisplayName(spec: Spec, server: Server): string {
    var parts = spec.suite.slice(0);
    parts.push(spec.description);
    return parts
        .filter(p => !!p)
        .join(' ');
}

export function getFullyQualifiedName(spec: Spec, server: Server): string {
    var parts = [];
    if (server.testContainerName) {
        parts.push(server.testContainerName);
    }
    var suite = spec.suite.slice(0);
    parts.push(suite.shift());
    suite.push(spec.description);
    parts.push(suite.join(' '));
    return parts
        .filter(p => !!p)
        .map(s => s.replace(/\./g, '-'))
        .join('.');
}

export function getTraits(spec: Spec, server: Server): Trait[] {
    return [];
}

The same module implemented in JavaScript:

exports.getDisplayName = function (spec, server) {
    var parts = spec.suite.slice(0);
    parts.push(spec.description);
    return parts.filter(function (p) { return !!p; }).join(' ');
};

exports.getFullyQualifiedName = function (spec, server) {
    var parts = [];
    if (server.testContainerName) {
        parts.push(server.testContainerName);
    }
    var suite = spec.suite.slice(0);
    parts.push(suite.shift());
    suite.push(spec.description);
    parts.push(suite.join(' '));
    return parts.
        filter(function (p) { return !!p; }).
        map(function (s) { return s.replace(/\./g, '-'); }).
        join('.');
};

exports.getTraits = function (spec, server) {
    return [];
};

Example: getDisplayName

Let's say the suites for a project represent classes and methods. In this case one might want the suites to be displayed separated by full stops. This can be implemented in an extension module.

Typescript:

export function getDisplayName(spec: Spec, server: Server): string {
    return spec.suite.join('.') + ' ' + spec.description;
}

JavaScript:

exports.getDisplayName = function (spec, server) {
    return spec.suite.join('.') + ' ' + spec.description;
};

Example: getFullyQualifiedName

Let's say the suites for a project represent classes and methods. In the class view the test explorer displays tests sorted by the inner most class name of the fully qualified name (as it would look in C#). If we want to show the full name of the classes in our project we can implement this in an extension module.

Typescript:

export function getFullyQualifiedName(spec: Spec, server: Server): string {
    var parts = [];

    // Add server.testContainerName to ensure uniqueness
    if (server.testContainerName) {
        parts.push(server.testContainerName);
    }

    // To ensure that the test explorer sees all suites as one identifier, we
    // separate suites with '-', not '.'
    parts.push(spec.suite.join('-'));

    // Add the test description
    parts.push(spec.description);

    // The fully qualified name is the parts separated by '.'. We make sure that
    // the parts do not contain '.', as this would confuse the test explorer
    return parts
        .map(s => s.replace(/\./g, '-'))
        .join('.');
}

JavaScript:

exports.getFullyQualifiedName = function (spec, server) {
    var parts = [];

    // Add server.testContainerName to ensure uniqueness
    if (server.testContainerName) {
        parts.push(server.testContainerName);
    }

    // To ensure that the test explorer sees all suites as one identifier, we
    // separate suites with '-', not '.'
    parts.push(spec.suite.join('-'));

    // Add the test description
    parts.push(spec.description);

    // The fully qualified name is the parts separated by '.'. We make sure that
    // the parts do not contain '.', as this would confuse the test explorer
    return parts.map(function (s) { return s.replace(/\./g, '-'); }).join('.');
};

Example: getTraits

Let's say we want to add the outer most suite as a trait for each test, while keeping any traits specified in KarmaTestAdapter.json. We can implement this in an extension module.

Typescript:

export function getTraits(spec: Spec, server: Server): Trait[]{
    var traits = spec.traits;
    var outerSuite = spec.suite[0];
    if (outerSuite) {
        traits.push({
            name: 'Suite',
            value: outerSuite
        });
    }
    return traits;
}

JavaScript:

exports.getTraits = function (spec, server) {
    var traits = spec.traits;
    var outerSuite = spec.suite[0];
    if (outerSuite) {
        traits.push({
            name: 'Suite',
            value: outerSuite
        });
    }
    return traits;
};

If we do not want to keep any traits specified in KarmaTestAdapter.json, we can simply ignore the existing traits.

Typescript:

export function getTraits(spec: Spec, server: Server): Trait[]{
    var traits: Trait[] = [];
    var outerSuite = spec.suite[0];
    if (outerSuite) {
        traits.push({
            name: 'Suite',
            value: outerSuite
        });
    }
    return traits;
}

JavaScript:

exports.getTraits = function (spec, server) {
    var traits = [];
    var outerSuite = spec.suite[0];
    if (outerSuite) {
        traits.push({
            name: 'Suite',
            value: outerSuite
        });
    }
    return traits;
};

Test containers

Test containers specify which Karma configuration files to use when running tests.

Changes

See http://mortenhoustonludvigsen.github.io/KarmaTestAdapter/changes/