# test-classes

`test-classes` is an object-oriented test framework for JavaScript.
It is a wrapper around the popular [Mocha](http://mochajs.org/) framework,
also including built-in [Sinon](sinonjs.org) mocking.

## Installation and Usage

##### Install the package and other dependencies
```
npm install -D test-classes mocha nyc
```

##### Write your first test
See the Examples section.

##### Run the test
```
nyc mocha '**/*.test.js'
```

## Examples

### Example 1: The Simple Example
Create and instantiate a new class that extends `test-classes`.
You must call `super` in the constructor with the `__dirname`
and a reference to the class you will be testing.
You must also call `exec` at the end of the constructor to run the tests.

In `Example1/index.js`:
```
module.exports = class Example1 {

    doSomething (arg) {
        if (5 === arg) throw new Error('Don\'t use 5');
    }

};
```

In `Example1/index.test.js`:
```
const Example1 = require('.');
const Test = require('test-classes');

new class Example1Test extends Test {

    constructor () {
        super(__dirname, Example1);

        this.testMethod('doSomething', this.testDoSomething);

        this.exec();
    }

    testDoSomething () {
        this.validCase('the value 7', () => {
            const myObject = new Example1();
            myObject.doSomething(7);
        });

        this.invalidCase('the value 5', () => {
            const myObject = new Example1();
            myObject.doSomething(5);
        });
    }

}();
```

### Example 2: The Async Example
`test-classes` supports asynchronous tests.
To use this feature, call `this.validAsyncCase` and `this.invalidAsyncCase`
instead of `this.validCase` and `this.invalidCase`.
For asynchronous test cases, you must return a promise.

In `Example2/index.js`:
```
module.exports = class Example2 {

    doSomethingAsync (arg) {
        return new Promise((resolve, reject) => {
            if ('foo' === arg) return reject(new Error('Don\'t use foo'));
            return resolve();
        });
    }

};
```

In `Example2/index.test.js`:
```
const Example2 = require('.');
const Test = require('test-classes');

new class Example2Test extends Test {

    constructor () {
        super(__dirname, Example2);

        this.testMethod('doSomethingAsync', this.testDoSomethingAsync);

        this.exec();
    }

    testDoSomethingAsync () {
        this.validAsyncCase('"jeff"', () => {
            const myObject = new Example2();
            return myObject.doSomethingAsync('jeff');
        });

        this.invalidAsyncCase('"foo"', () => {
            const myObject = new Example2();
            return myObject.doSomethingAsync('foo');
        });
    }

}();
```

### Example 3: The Explicit Example
The ability to assert results or error messages is also built in.
You must return a value in order for this to work.

In `Example3/index.js`:
```
module.exports = class Example3 {

    add (lhs, rhs) {
        if (!lhs || !rhs) throw new Error ('Pass both args please.');
        return lhs + rhs;
    }

    addAsync (lhs, rhs) {
        return new Promise((resolve, reject) => {
            if (!lhs || !rhs) return reject(new Error('Pass both args please.'));
            return resolve(lhs + rhs);
        });
    }

};
```

In `Example3/index.test.js`:
```
const Example3 = require('.');
const Test = require('test-classes');

new class Example3Test extends Test {

    constructor () {
        super(__dirname, Example3);

        this.testMethod('add', this.testAdd);
        this.testMethod('addAsync', this.testAddAsync);

        this.exec();
    }

    testAdd () {
        this.validCase('two numbers', () => {
            const myObject = new Example3();
            return myObject.add(10, 4);
        }, 14);

        this.invalidCase('two numbers', () => {
            const myObject = new Example3();
            return myObject.add(10);
        }, 'Pass both args please.');
    }

    testAddAsync () {
        this.validAsyncCase('two numbers', () => {
            const myObject = new Example3();
            return myObject.addAsync(10, 4);
        }, 14);

        this.invalidAsyncCase('two numbers', () => {
            const myObject = new Example3();
            return myObject.addAsync(10);
        }, 'Pass both args please.');
    }

}();
```

### Example 4: The Stub Example
You can stub methods without worrying about un-stubbing them.
They'll be recreated for each test case, and dismantled at the end of each.

In `Example4/index.js`:
```
module.exports = class Example4 {

    static getPrefix () {
        return 'the-real-';
    }

    prefix (arg) {
        return Example4.getPrefix() + arg;
    }

};
```

In `Example4/index.test.js`:
```
const Example4 = require('.');
const Test = require('test-classes');

new class Example4Test extends Test {

    constructor () {
        super(__dirname, Example4);

        this.testMethod('prefix', this.testPrefix);

        this.exec();
    }

    testPrefix () {
        this.stub(Example4, 'getPrefix', () => 'the-stubbed-');

        this.validCase('Jeff Hudson', () => {
            const myObject = new Example4();
            return myObject.prefix('hudson155');
        }, 'the-stubbed-hudson155');
    }

}();
```
