51

I work with Typescript on an AngularJS 1.X project. I use different Javascript libraries for different purposes. To unit-test my source I would like to stub some dependencies using the Typings (= interfaces). I don't want to use the ANY-type and neither to write an empty method for each interface method.

Im looking for a way to do something like that:

let dependency = stub(IDependency);
stub(dependency.b(), () => {console.log("Hello World")});
dependency.a(); // --> Compile, do nothing, no exception
dependency.b(); // --> Compile, print "Hello World", no exception

The pain I have right now, is that I either use any and implement all methods which get called in my test case or I implement the interface and implement the full interface. That's too much useless code :(.

How can I generate an object that has an empty implementation for each method and is typed? I use Sinon for mocking purposes, but im open to use other libraries too.

PS: I know that Typescript erases the interfaces...but I still would like to solve that :).

user1879408
  • 1,948
  • 3
  • 17
  • 27

8 Answers8

47

I have been writing Typescript tests using qUnit and Sinon, and I have experienced exactly the same pain you are describing.

Let's assume you have a dependency on an interface like:

interface IDependency {
    a(): void;
    b(): boolean;
}

I have managed to avoid the need of additional tools/libraries by using a couple of approaches based on sinon stubs/spies and casting.

  • Use an empty object literal, then directly assign sinon stubs to the functions used in the code:

    //Create empty literal as your IDependency (usually in the common "setup" method of the test file)
    let anotherDependencyStub = <IDependency>{};
    
    //Set stubs for every method used in your code 
    anotherDependencyStub.a = sandbox.stub(); //If not used, you won't need to define it here
    anotherDependencyStub.b = sandbox.stub().returns(true); //Specific behavior for the test
    
    //Exercise code and verify expectations
    dependencyStub.a();
    ok(anotherDependencyStub.b());
    sinon.assert.calledOnce(<SinonStub>anotherDependencyStub.b);
    
  • Use object literal with empty implementations of the methods needed by your code, then wrap methods in sinon spies/stubs as required

    //Create dummy interface implementation with only the methods used in your code (usually in the common "setup" method of the test file)
    let dependencyStub = <IDependency>{
        a: () => { }, //If not used, you won't need to define it here
        b: () => { return false; }
    };
    
    //Set spies/stubs
    let bStub = sandbox.stub(dependencyStub, "b").returns(true);
    
    //Exercise code and verify expectations
    dependencyStub.a();
    ok(dependencyStub.b());
    sinon.assert.calledOnce(bStub);
    

They work quite nice when you combine them with sinon sandboxes and common setup/teardown like the one provided by qUnit modules.

  • In the common setup you create a new sandbox and the mock object literals for your dependencies.
  • In the test you just specify the spies/stubs.

Something like this (using the first option, but would work the same way if you were using the second option):

QUnit["module"]("fooModule", {
    setup: () => {
        sandbox = sinon.sandbox.create();
        dependencyMock = <IDependency>{};
    },
    teardown: () => {
        sandbox.restore();
    }
});

test("My foo test", () => {
    dependencyMock.b = sandbox.stub().returns(true);

    var myCodeUnderTest = new Bar(dependencyMock);
    var result = myCodeUnderTest.doSomething();

    equal(result, 42, "Bar.doSomething returns 42 when IDependency.b returns true");
});

I would agree this is still not the ideal solution but it works reasonably well, doesn't require extra libraries and keeps the amount of extra code needed to a low manageable level.

Daniel J.G.
  • 34,266
  • 9
  • 112
  • 112
  • I've recently found [@salesforce/ts-sinon](https://www.npmjs.com/package/@salesforce/ts-sinon) to be useful for this, as it includes a `stubInterface` method (as well as other methods such as `fromStub`) which makes using Sinon in TypeScript much nicer. – ostrumvulpes Jan 21 '21 at 18:18
16

Latest TypeMoq (ver 1.0.2) supports mocking TypeScript interfaces, as long as the runtime (nodejs/browser) supports the Proxy global object introduced by ES6.

So, assuming IDependency looks like this:

interface IDependency {
    a(): number;
    b(): string;
}

then mocking it with TypeMoq would be as simple as this:

import * as TypeMoq from "typemoq";
...
let mock = TypeMoq.Mock.ofType<IDependency>();

mock.setup(x => x.b()).returns(() => "Hello World");

expect(mock.object.a()).to.eq(undefined);
expect(mock.object.b()).to.eq("Hello World");
florinn
  • 179
  • 1
  • 3
15

I think the short answer is that this is not possible in Typescript, as the language offers no compile-time or run-time "reflection". It's not possible for a mock library to iterate the members of an interface.

See thread: https://github.com/Microsoft/TypeScript/issues/1549

This is unfortunate for TDD developers, in which mocking a dependency is a central part of the development workflow.

There are a number of techniques for quickly stubbing the methods, however, as described by the other answers. These options might do the job, with a little mental adjustment.

Edit: The Typescript Abstract Syntax Tree, AST, is a compile-time "introspection" - which could probably be used to generate mocks. However, I don't know if anyone has made a practical library.

Jørgen Tvedt
  • 1,214
  • 3
  • 12
  • 23
6

From npmjs:

Mocking interfaces
You can mock interfaces too, just instead of passing type to mock function, set mock function generic type Mocking interfaces requires Proxy implementation

let mockedFoo:Foo = mock<FooInterface>(); // instead of mock(FooInterface)
const foo: SampleGeneric<FooInterface> = instance(mockedFoo);

ts-mockito supports mocking interfaces since version 2.4.0:

malat
  • 12,152
  • 13
  • 89
  • 158
2

There are few libraries that allows to do that TypeMoq, TeddyMocks and Typescript-mockify are probably one of the more popular ones.

Check the github repositories and pick the one you like better : links:

You can also use more popular libs like Sinon, but first you have to use an <any> type and then narrow it to <IDependency> type (How do I use Sinon with Typescript?)

Community
  • 1
  • 1
PolishDeveloper
  • 930
  • 5
  • 17
  • They all need a class to create a mock, an interface is not enough. I guess the type erasure makes it impossible, without hacking Typescript itself --> http://stackoverflow.com/questions/13142635/how-can-i-create-an-object-based-on-an-interface-file-definition-in-typescript – user1879408 May 04 '16 at 13:33
  • What about creating an empty object that implements your interface ? and pass it as a object to your mock ? – PolishDeveloper May 04 '16 at 13:41
  • That does not create the methods --> Type erasure ;) – user1879408 May 04 '16 at 14:34
  • 1
    Right, then only solution is to create a tool that does that :/ – PolishDeveloper May 04 '16 at 14:47
0

You can try moq.ts, but it depends on Proxy object

interface IDependency {
  a(): number;
  b(): string;
}


import {Mock, It, Times} from 'moq.ts';

const mock = new Mock<IDependency>()
  .setup(instance => instance.a())
  .returns(1);

mock.object().a(); //returns 1

mock.verify(instance => instance.a());//pass
mock.verify(instance => instance.b());//fail
dvabuzyarov
  • 139
  • 1
  • 4
0

SafeMock is quite nice, but sadly seems like it is unmaintained now. Full disclosure, I used to work with the author.

import SafeMock, {verify} from "safe-mock";

const mock = SafeMock.build<SomeService>();

// specify return values only when mocks are called with certain arguments like this
when(mock.someMethod(123, "some arg")).return("expectedReturn");

// specify thrown exceptions only when mocks are called with certain arguments like this
when(mock.someMethod(123, "some arg")).throw(new Error("BRR! Its cold!")); 

// specify that the mock returns rejected promises with a rejected value with reject
when(mock.someMethod(123)).reject(new Error("BRR! Its cold!"));

//use verify.calledWith to check the exact arguments to a mocked method
verify(mock.someMethod).calledWith(123, "someArg");

SafeMock won't let you return the wrong type from mocks.

interface SomeService {
    createSomething(): string;
}

const mock: Mock<SomeService> = SafeMock.build<SomeService>();

//Won't compile createSomething returns a string
when(mock.createSomething()).return(123); 
James McMahon
  • 48,506
  • 64
  • 207
  • 283
-6

Now it's possible. I released an enhanced version of the typescript compiler that makes interfaces metadata available at runtime. For example, you can write:

interface Something {

}

interface SomethingElse {
    id: number;
}

interface MyService {
    simpleMethod(): void;
    doSomething(p1: number): string;
    doSomethingElse<T extends SomethingElse>(p1: Something): T;
}

function printMethods(interf: Interface) {
    let fields = interf.members.filter(m => m.type.kind === 'function'); //exclude methods.
    for(let field of fields) {
        let method = <FunctionType>field.type;
        console.log(`Method name: ${method.name}`);
        for(let signature of method.signatures) {
            //you can go really deeper here, see the api: reflection.d.ts
            console.log(`\tSignature parameters: ${signature.parameters.length} - return type kind: ${signature.returns.kind}`);
            if(signature.typeParameters) {
                for(let typeParam of signature.typeParameters) {
                    console.log(`\tSignature type param: ${typeParam.name}`); //you can get constraints with typeParam.constraints
                }
            }
            console.log('\t-----')
        }
    }
}

printMethods(MyService); //now can be used as a literal!!

and this is the output:

$ node main.js
Method name: simpleMethod
        Signature parameters: 0 - return type kind: void
        -----
Method name: doSomething
        Signature parameters: 1 - return type kind: string
        -----
Method name: doSomethingElse
        Signature parameters: 1 - return type kind: parameter
        Signature type param: T
        -----

With all these information, you can build stubs programmatically, as you prefer.

You can find my project here.

pcan
  • 893
  • 11
  • 24
  • 16
    Suggesting your personal fork of typescript doesn't actually answer the question-- it's usually assumed that when people mention a language in a question, they mean an official release of that language. Hence my downvote. – Maus Feb 22 '19 at 00:58
  • 2
    @Maus It's your opinion. The question asks "How to stub a Typescript-Interface / Type-definition?". The answer provides a way to do this. If you read the official Typescript issues on github A LOT of people is trying to do this, but the team doesn't care at all, and does not give any means to do this in a clean way. I proved that this kind of thing is *feasible*: if many people ask for this feature, maybe the Typescript core team will listen to users requests. – pcan Feb 22 '19 at 11:57
  • 9
    I think it's impressive and important work, but i still don't think it's a great answer for this question – Maus Feb 22 '19 at 16:07