npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

unit-test-class

v1.0.7

Published

Automatically mock javascript classes to make unit tests faster and better

Downloads

11

Readme

Unit test class

Greenkeeper badge

NPM Version Build Status Test Coverage

Easy mock your es6 + classes.

Unit tests classes was created from a great conflict, the desire to create great stuff with great tests coverage VS the time invested to unit testing classes.

Unit test definition from Wiki: Intuitively, one can view a unit as the smallest testable part of an application. In procedural programming, a unit could be an entire module, but it is more commonly an individual function or procedure. In object-oriented programming, a unit is often an entire interface, such as a class, but could be an individual method.

Unit test class module addresses the last part could be an individual method.

This module is completely framework agnostic and was build without any framework dependencies, to make sure you can use all of these cool frameworks:

  • Jasmine
  • Mocha
  • AVA
  • Tape
  • Jest
  • Sinon
  • And more!

Test faster

Writing unit-test taked a lot of time. Mainlly because we need to re-create isolated scenarios where we can test the smallest parts. "Unit test class" - is here to help by removing all the tedious boilerplate code from your unit tests.

Test better

It's sometimes very difficult to gain total isolation to the unit your testing. Therefore, it's very common and wrong some developers chose to test a method by not noticing it's inflaunced by the other method result They have creted an integration test, not unit test .

##Api

###TL;DR

//You have a class

class YourClass{
    constructor(name){
        this._name = name;
    }
    get myName(){
        return `My name is ${this._name}`
    }
    hi(userName){
        if (userName)
            this.log(`Hi ${userName},${this.myName}`);
        else
            this.log('Hi, What\'s you name?')
    }
    log(string){
        console.log(string)
    }
}
//You can test it like that
const chai = require('chai');
chai.use(require('chai-spies'));
const expect = chai.expect;

const {MockedClassService} = require('unit-test-class');

const mockService = new MockedClassService(chai.spy);
describe('YourClass' , ()=>{
    const YourClassFactory = mockService.mock(YourClass);
    let mockView;
    describe('#hi' , ()=>{
        let getMyName;
        beforeEach(()=>{
            getMyName = chai.spy(()=>'myName');
            mockView = YourClassFactory.test('hi').spies({
                    get myName(){
                        return getMyName();
                    }
                })
                .create();
            
        })
        it('Should log with user name' , ()=>{
            const {instance , spies} = mockView;

            instance.hi('bob'); 

            //Spy on a getter
            expect(getMyName).to.have.been.called();

            //Spy on internal method
            expect(instance.log).to.have.been.called.with('Hi bob,myName');
            //OR alterntivally
            expect(spies.log_function).to.have.been.called.with('Hi bob,myName');
        })
        it('Should ask user name' , ()=>{
            const {instance , spies} = mockView;

            instance.hi(); 

            //Spy on a getter
            expect(getMyName).to.not.have.been.called();

            //Spy on internal method
            expect(instance.log).to.have.been.called.with('Hi, What\'s you name?');
            //OR alterntivally
            expect(spies.log_function).to.have.been.called.with('Hi, What\'s you name?');
        })
    })
});

MockedClassService

An intance of MockedClassService will create for us a mockFactory.

MockedClassService accepts only one variable SpyFactory which is a method that returns a spy, the implemantion is very specific to the framework your are using.

This is an example with using chai spies module

const chai = require('chai');
chai.use(require('chai-spies'));
const mockService = new MockedClassService(chai.spy);

It is not required to initialize this service more than once. However, it's not harmful - so do what's more comfortable for you.

mock()

This is the only method you should use on MockedClassService. It returns a MockedClassFactory.

    const mockFactory = mockService.mock(/*..Class you will be testing..*/);

You should call it once per class. However, it's not harmful to call it more - so do what's more comfortable for you.

MockedClassFactory

An instance of MockedClassFactory wraps you original class and will be able to create mocked instance per call.

You should not initilize it your self - you should be geting this from the mock service by calling mockSerive.mock(/*..Class you will be testing..*/).

test()

This is a chinable method that returns a new MockedClassFactory instance.

This method accepts both single string and array with multiple strings, these are the keys of the class you are planing to test! Meaning that these keys are the only ones that won't be mocked.

This method only creates a new MockedClassFactory instance, pre-configured with the new configuration.

mockFactory.test('constructor');
//Or...
mockFactory.test(['constructor' , 'method' , 'property']);

Important! When your class extends a different parent class. Your mocked class will extend a mocked class. Meanining the original constructor will not run, instead a 'SuperSpy' consturctor will run.

This will help you to make sure you class is calling the super consturctor as it should - again something that will require a lot of boilerplate code with this module.

class Parent{
        constructor(i){
           /*
            When testing class Child in some cases you don't want to 
            start the construction of the Parent class.
            Warning! 
            You should remeber to do also integration tests.
            With these you must test the full integration of Child and Parent.
           */
        }
    }
    class Child extends Parent{
        constructor(i){
            super(i+1);
        }
    }
    const mockService = new MockedClassService(chai.spy);
    const mockFactory = mockService.mock(Child);
    const mockView = mockFactory.test('constructor').create(1);
    expect(mockView.spies.super).to.have.been.called.with(2);

spies()

This is a chinable method that returns a new MockedClassFactory instance. With a new instance created the module will put spies instead of the original methods and properties. Sometimes you will need to create a custome spy, this method let's you setup these special spies.

This method accepts a single object. The keys are the method/property names you want to be setup with the sepcial spy. The values are these special spies.

This method only creates a new MockedClassFactory instance, pre-configured with the new configuration.

mockFactory.spies({
    method : chai.spy(()=>"from test"),
    property : chai.spy(()=>"from test")
});

create()

This method creates a new mockView instance. On it you will find your mocked instance and spies as key/value.

It accepts any arugments and will forward these to your class costructor as is.

const mockView = mockFactory.test('method').create(1,2);
const myInstance = mockView.instance;
const spies = mockView.spies;

myInstance.method();

//If it's a function you can access it's spy like this
expect(myInstance.otherMethod).to.have.been.called();
//Or...
expect(spies.otherMethod_function).to.have.been.called();

/*If it's a property will replace it with a getter/setter
* Because of the nature of getter/setter you will only
* be able to use it from the spies object
*/
expect(spies.name_get).to.have.been.called.with('some_value');

Few things you should know

  • spies object as a strict covention:
    • functions will be defined as ${name}_function.
    • value will be switched to getter/setter.
    • getters/setters will be defined as ${name}_get and ${name}_set.

##Examples

Test a mothod without constructor

A common mistake when trying to tese method method of the following class.

class TestMe extends Something{
    constructor(options){
        super(options);
        this.somethingVeryRelatedToOptions(options);
        this.method(options.ping);
    }
    somethingVeryRelatedToOptions(options){
        /..Some code../
    }
    method(ping){
        if (ping == 'ping')
            this.pong = 'pong';
    }
}

The mistake would be to

const testMe = new TestMe({/*..A lot of config..*/});
testMe.method('ping');
expect(testMe.pong).to.equal('pong');

Notice the when testing method:

  • we have also tested somethingVeryRelatedToOptions logic.
  • The Something class constructor and who knows we have got there...

This mistake will make the unit tests very breakable as a change in one of Something or somethingVeryRelatedToOptions will make all our tests fail.

There are a lot of approaches to this correctly, while I have nothing against, these, these just take way more time mocking the required methods and greater developer proficiency.

This module will make it much easier for you

const mockService = new MockedClassService(chai.spy);// A service could be defined once...
const mockFactory = mockService.mock(TestMe);// A factory could be defined once per class
const testMe = mockView.instance;
testMe.method('ping');
expect(testMe.pong).to.equal('pong')

And a full test case example with Chai and chai spies

const mockService = new MockedClassService(chai.spy); // A service could be defined once...

describe('TestMe - example with chai' , ()=>{
    let mockFactory = mockService.mock(TestMe); // A factory could be defined once per class
    describe('#constructor' , ()=>{
        const options = {};
        const mock = mockFactory.test('constructor').create(options);
        expect(
            mock.spies.somethingVeryRelatedToOptions_function
        ).to.have.been.called.with(options)
    })
    describe('#method' , ()=>{
        let mockView;
        let testMe;
        beforeEach(()=>{
            mockView = mockFactory.test('method').create({});
            testMe = mockView.instance;
        })
        it('Should set `pong` if given `ping`' , ()=>{
            testMe.method('ping');
            expect(testMe.pong).to.equal('pong')
        })
        it('Shouldn\'t set `pong` if not given `ping`' , ()=>{
            testMe.method('dsfsdf');
            expect(testMe.pong).to.not.equal('pong')
        })   
    });
})

Better support for TypeScript projects

const mockService = new MockedClassService(chai.spy);// A service could be defined once...
const mockFactory = mockService.mock<TestMe>(TestMe);// A factory could be defined once per class
const testMe = mockView.instance;
//Notice that both `pong` property and method `method` are recognized by TypeScript
testMe.method('ping');
expect(testMe.pong).to.equal('pong')

License

MIT