Je travaille avec TypeScript sur un projet AngularJS 1.X. J'utilise différentes bibliothèques Javascript à des fins différentes. Pour tester mon source, je voudrais remplacer certaines dépendances à l’aide des Typings (= interfaces). Je ne veux pas utiliser le type ANY ni écrire une méthode vide pour chaque méthode d'interface.
Je cherche un moyen de faire quelque chose comme ça:
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
La douleur que j'ai maintenant, c’est que j’utilise soit any
et implémente toutes les méthodes qui sont appelées dans mon scénario de test, soit que j’implémente l'interface et implémente l'interface complète. C'est trop de code inutile :(.
Comment générer un objet avec une implémentation vide pour chaque méthode et typé? J'utilise Sinon à des fins moqueuses, mais je suis ouvert à utiliser d'autres bibliothèques aussi.
PS: Je sais que TypeScript efface les interfaces ... mais je voudrais quand même résoudre ce problème :).
Je pense que la réponse courte est que ceci est not possible dans TypeScript, car le langage n’offre aucune "réflexion" au moment de la compilation ou de l’exécution. Il n'est pas possible pour une bibliothèque fictive d'itérer les membres d'une interface.
Voir le fil: https://github.com/Microsoft/TypeScript/issues/1549
Cela est regrettable pour les développeurs TDD, dans lesquels le moquage d’une dépendance est un élément central du flux de travail de développement.
Cependant, il existe un certain nombre de techniques pour écraser rapidement les méthodes, comme décrit dans les autres réponses. Ces options pourraient faire l'affaire avec un peu d'adaptation mentale.
J'ai écrit des tests TypeScript en utilisant qUnit et Sinon, et j'ai vécu exactement la même douleur que celle que vous décrivez.
Supposons que vous ayez une dépendance sur une interface telle que:
interface IDependency {
a(): void;
b(): boolean;
}
J'ai réussi à éviter le besoin d'outils/bibliothèques supplémentaires en utilisant quelques approches basées sur des talons/espions et du casting.
Utilisez un littéral d'objet vide, puis assignez directement les autres fonctions aux fonctions utilisées dans le 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);
Utilisez littéral d'objet avec des implémentations vides des méthodes nécessaires à votre code, puis enveloppez les méthodes dans spies/stubs, si nécessaire
//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);
Ils fonctionnent assez bien lorsque vous les combinez avec des sandbox et une configuration/démontage courante comme celle fournie par les modules qUnit.
Quelque chose comme ça (en utilisant la première option, mais fonctionnerait de la même manière si vous utilisiez la deuxième 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");
});
Je conviens que ce n’est pas encore la solution idéale, mais cela fonctionne raisonnablement bien, ne nécessite pas de bibliothèques supplémentaires et maintient la quantité de code supplémentaire nécessaire à un niveau peu gérable.
Latest TypeMoq (version 1.0.2) prend en charge le moquage d'interfaces TypeScript, tant que le moteur d'exécution (nodejs/browser) prend en charge l'objet global Proxy introduit par ES6.
Donc, en supposant que IDependency
ressemble à ceci:
interface IDependency {
a(): number;
b(): string;
}
puis se moquer avec TypeMoq serait aussi simple que cela:
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");
Maintenant c'est possible. J'ai publié une version améliorée du compilateur TypeScript qui rend les métadonnées d'interface disponibles au moment de l'exécution. Par exemple, vous pouvez écrire:
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!!
et voici la sortie:
$ 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
-----
Avec toutes ces informations, vous pouvez créer des stubs à l'aide de programmes, comme vous préférez.
Vous pouvez trouver mon projet ici .
Il y a peu de bibliothèques qui permettent de faire que TypeMoq
, TeddyMocks
et TypeScript-mockify
soient probablement l'une des plus populaires.
Vérifiez les dépôts de github et choisissez celui qui vous convient le mieux: Liens:
Vous pouvez également utiliser des bibliothèques plus populaires comme Sinon, mais vous devez d'abord utiliser un type <any>
puis le réduire au type <IDependency>
( Comment utiliser Sinon avec Typescript? )
Vous pouvez essayer moq.ts , mais cela dépend de l’objet Proxy
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