web-dev-qa-db-fra.com

Comment stub une TypeScript-Interface/Type-definition?

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

17
user1879408

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.

7
Jørgen Tvedt

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. 

  • Dans la configuration commune, vous créez un nouveau sandbox et les littéraux d'objet fictif pour vos dépendances.
  • Dans le test, il vous suffit de spécifier les espions/talons.

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.

20
Daniel J.G.

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");
11
florinn

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 .

3
pcan

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? )

2
PolishDeveloper

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
0
dvabuzyarov