J'ai les modules ES6 suivants:
network.js
export function getDataFromServer() {
return ...
}
widget.js
import { getDataFromServer } from 'network.js';
export class Widget() {
constructor() {
getDataFromServer("dataForWidget")
.then(data => this.render(data));
}
render() {
...
}
}
Je cherche un moyen de tester Widget avec une instance simulée de getDataFromServer
. Si j'utilisais des <script>
s séparés au lieu de modules ES6, comme dans Karma, je pourrais écrire mon test comme:
describe("widget", function() {
it("should do stuff", function() {
let getDataFromServer = spyOn(window, "getDataFromServer").andReturn("mockData")
let widget = new Widget();
expect(getDataFromServer).toHaveBeenCalledWith("dataForWidget");
expect(otherStuff).toHaveHappened();
});
});
Cependant, si je teste les modules ES6 individuellement en dehors d'un navigateur (comme avec Mocha + babel), j'écrirais quelque chose comme:
import { Widget } from 'widget.js';
describe("widget", function() {
it("should do stuff", function() {
let getDataFromServer = spyOn(?????) // How to mock?
.andReturn("mockData")
let widget = new Widget();
expect(getDataFromServer).toHaveBeenCalledWith("dataForWidget");
expect(otherStuff).toHaveHappened();
});
});
D'accord, mais maintenant getDataFromServer
n'est pas disponible dans window
(enfin, il n'y en a pas window
du tout), et je ne connais pas un moyen d'injecter des trucs directement dans son widget.js
propre scope.
Alors, où dois-je partir d'ici?
- Existe-t-il un moyen d'accéder à la portée de
widget.js
, ou au moins de remplacer ses importations par mon propre code? - Sinon, comment puis-je rendre
Widget
testable?
Les choses que j'ai envisagées:
une. Injection manuelle de dépendances.
Supprimez toutes les importations widget.js
et attendez de l'appelant qu'il fournisse les deps.
export class Widget() {
constructor(deps) {
deps.getDataFromServer("dataForWidget")
.then(data => this.render(data));
}
}
Je suis très mal à l'aise de gâcher l'interface publique de Widget comme celle-ci et d'exposer les détails de mise en œuvre. Ne pas aller.
b. Exposez les importations pour permettre de les moquer.
Quelque chose comme:
import { getDataFromServer } from 'network.js';
export let deps = {
getDataFromServer
};
export class Widget() {
constructor() {
deps.getDataFromServer("dataForWidget")
.then(data => this.render(data));
}
}
puis:
import { Widget, deps } from 'widget.js';
describe("widget", function() {
it("should do stuff", function() {
let getDataFromServer = spyOn(deps.getDataFromServer) // !
.andReturn("mockData");
let widget = new Widget();
expect(getDataFromServer).toHaveBeenCalledWith("dataForWidget");
expect(otherStuff).toHaveHappened();
});
});
Ceci est moins invasif mais m'oblige à écrire beaucoup de passe-partout pour chaque module, et il y a toujours un risque que j'utilise getDataFromServer
au lieu de deps.getDataFromServer
tout le temps. Je suis inquiet à ce sujet, mais c'est ma meilleure idée jusqu'à présent.
createSpy
( github.com/jasmine/jasmine/blob/… ) avec une référence importée à getDataFromServer à partir du module 'network.js'. Pour que, dans le fichier de tests du widget, vous importiez getDataFromServer, puis vous feriezlet spy = createSpy('getDataFromServer', getDataFromServer)
spyOn
sur cet objet, importé dunetwork.js
module. C'est toujours une référence au même objet.Widget
l'interface publique de?Widget
est foiré sansdeps
. Pourquoi ne pas rendre la dépendance explicite?Réponses:
J'ai commencé à utiliser le
import * as obj
style dans mes tests, qui importe toutes les exportations d'un module en tant que propriétés d'un objet qui peuvent ensuite être simulées. Je trouve que c'est beaucoup plus propre que d'utiliser quelque chose comme rewire ou proxyquire ou toute autre technique similaire. Je l'ai fait le plus souvent lorsque j'ai besoin de simuler des actions Redux, par exemple. Voici ce que je pourrais utiliser pour votre exemple ci-dessus:Si votre fonction se trouve être une exportation par défaut, alors
import * as network from './network'
produirait{default: getDataFromServer}
et vous pouvez vous moquer de network.default.la source
import * as obj
seul dans le test ou également dans votre code régulier?[method_name] is not declared writable or has no setter
ce qui a du sens puisque les importations es6 sont constantes. Existe-t-il un moyen de contourner le problème?import
(contrairement àrequire
, qui peut aller n'importe où) est hissé, vous ne pouvez donc pas techniquement importer plusieurs fois. On dirait que votre espion est appelé ailleurs? Afin d'éviter que les tests ne gâchent l'état (connu sous le nom de pollution de test), vous pouvez réinitialiser vos espions dans un afterEach (par exemple, sinon.sandbox). Jasmine, je crois, fait cela automatiquement.import
dans leur JS n'utilisent pas vraiment les modules ES6. Quelque chose comme webpack ou babel interviendra au moment de la construction et le convertira soit en son propre mécanisme interne pour appeler des parties distantes du code (par exemple__webpack_require__
), soit en l'un des standards de facto pré-ES6 , CommonJS, AMD ou UMD. Et cette conversion ne respecte souvent pas strictement les spécifications. Donc, pour beaucoup de développeurs en ce moment, cette réponse fonctionne très bien. Pour l'instant.@carpeliam est correct mais notez que si vous voulez espionner une fonction dans un module et utiliser une autre fonction dans ce module appelant cette fonction, vous devez appeler cette fonction dans le cadre de l'espace de noms des exportations, sinon l'espion ne sera pas utilisé.
Mauvais exemple:
Bon exemple:
la source
exports.myfunc2
est une référence directe àmyfunc2
jusqu'à lespyOn
remplace par une référence à une fonction d'espionnage.spyOn
changera la valeur deexports.myfunc2
et le remplacera par un objet espion, alors qu'ilmyfunc2
reste intact dans la portée du module (carspyOn
il n'y a pas accès)*
geler l'objet et les attributs de l'objet ne peuvent pas être modifiés?export function
avecexports.myfunc2
est techniquement un mélange de syntaxe de module commonjs et ES6 et cela n'est pas autorisé dans les versions plus récentes de webpack (2+) qui nécessitent l'utilisation de la syntaxe du module ES6 tout ou rien. J'ai ajouté une réponse ci-dessous basée sur celle-ci qui fonctionnera dans les environnements stricts ES6.J'ai implémenté une bibliothèque qui tente de résoudre le problème de la simulation au moment de l'exécution des importations de classes Typescript sans avoir besoin de la classe d'origine pour connaître toute injection de dépendance explicite.
La bibliothèque utilise la
import * as
syntaxe, puis remplace l'objet exporté d'origine par une classe stub. Il conserve la sécurité de type afin que vos tests soient interrompus lors de la compilation si un nom de méthode a été mis à jour sans mettre à jour le test correspondant.Cette bibliothèque se trouve ici: ts-mock-importations .
la source
La réponse de @ vdloo m'a poussé dans la bonne direction, mais l'utilisation des mots-clés commonjs "exports" et du module ES6 "export" ensemble dans le même fichier ne fonctionnait pas pour moi (webpack v2 ou ultérieur se plaint). Au lieu de cela, j'utilise une exportation par défaut (variable nommée) enveloppant toutes les exportations de modules nommés individuels, puis j'importe l'exportation par défaut dans mon fichier de tests. J'utilise la configuration d'exportation suivante avec mocha / sinon et le stubbing fonctionne bien sans avoir besoin de recâblage, etc.:
la source
let MyModule
n'est pas nécessaire pour utiliser l'exportation par défaut (il peut s'agir d'un objet brut). De plus, cette méthode ne nécessite pasmyfunc1()
d'appelermyfunc2()
, elle fonctionne simplement pour l'espionner directement.J'ai trouvé que cette syntaxe fonctionnait:
Mon module:
Code de test de mon module:
Voir la doc .
la source
jest.mock()
doit correspondre au nom utilisé dans import / packge.json au lieu du nom de la constante. Dans la documentation, ils sont tous les deux identiques, mais avec un code commeimport jwt from 'jsonwebtoken'
vous devez configurer le simulacre commejest.mock('jsonwebtoken')
Je ne l'ai pas essayé moi-même, mais je pense que la moquerie pourrait fonctionner. Il vous permet de remplacer le module réel par une maquette que vous avez fournie. Voici un exemple pour vous donner une idée de son fonctionnement:
Il semble que ce
mockery
n'est plus maintenu et je pense que cela ne fonctionne qu'avec Node.js, mais néanmoins, c'est une solution intéressante pour se moquer des modules qui sont autrement difficiles à se moquer.la source