Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to stub a Typescript-Interface / Type-definition?

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 :).

like image 759
user1879408 Avatar asked May 04 '16 12:05

user1879408


People also ask

How do you define a TypeScript interface?

In TypeScript, an interface is an abstract type that tells the compiler which property names a given object can have. TypeScript creates implicit interfaces when you define an object with properties. It starts by looking at the object's property name and data type using TypeScript's type inference abilities.

What is stub in TypeScript?

It's a simple function that takes a Partial as a parameter and returns that partial casted as the desired type. Here's the previous post's example, with using the stub function instead of casting the dependency to type any .

Is interface a type in TypeScript?

TypeScript Interface TypeTypeScript allows you to specifically type an object using an interface that can be reused by multiple objects. To create an interface, use the interface keyword followed by the interface name and the typed object.

How do I inherit an interface in TypeScript?

Interfaces and Inheritance An interface can be extended by other interfaces. In other words, an interface can inherit from other interface. Typescript allows an interface to inherit from multiple interfaces. Use the extends keyword to implement inheritance among interfaces.


1 Answers

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.

like image 89
Daniel J.G. Avatar answered Sep 21 '22 01:09

Daniel J.G.