How to stub a Typescript-Interface / Type-definition?
Solution 1
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.
Solution 2
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.
Solution 3
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");
Solution 4
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:
Solution 5
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:
- TeddyMocks: https://github.com/mbraude/TeddyMocks
- TypeMoq: https://github.com/florinn/typemoq
- TypeScriptMockify: https://github.com/brechtbilliet/typescript-mockify
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?)
Related videos on Youtube
user1879408
Updated on January 14, 2021Comments
-
user1879408 over 3 years
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 about 8 yearsThey 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 --> stackoverflow.com/questions/13142635/…
-
PolishDeveloper about 8 yearsWhat about creating an empty object that implements your interface ? and pass it as a object to your mock ?
-
user1879408 about 8 yearsThat does not create the methods --> Type erasure ;)
-
PolishDeveloper about 8 yearsRight, then only solution is to create a tool that does that :/
-
Maus about 5 yearsSuggesting 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.
-
pcan about 5 years@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.
-
Maus about 5 yearsI think it's impressive and important work, but i still don't think it's a great answer for this question
-
James McMahon over 4 yearsThis is wrong, there is are few libraries that accomplish type safety, see other answers for a few examples.
-
ostrumvulpes over 3 yearsI've recently found @salesforce/ts-sinon to be useful for this, as it includes a
stubInterface
method (as well as other methods such asfromStub
) which makes using Sinon in TypeScript much nicer.