Eu tenho os seguintes módulos ES6:
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() {
...
}
}
Estou procurando uma maneira de testar o Widget com uma instância simulada de getDataFromServer
. Se eu usasse <script>
s separados em vez de módulos ES6, como no Karma, poderia escrever meu teste como:
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();
});
});
No entanto, se eu estiver testando os módulos ES6 individualmente fora de um navegador (como no Mocha + babel), escreveria algo como:
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();
});
});
Ok, mas agora getDataFromServer
não está disponível no window
(bem, não existe window
) e não sei como injetar coisas diretamente no widget.js
próprio escopo.
Então, para onde eu vou daqui?
- Existe uma maneira de acessar o escopo
widget.js
ou, pelo menos, substituir suas importações por meu próprio código? - Caso contrário, como posso fazer o teste
Widget
?
Coisas que eu considerei:
uma. Injeção de dependência manual.
Remova todas as importações widget.js
e espere que o chamador forneça os deps.
export class Widget() {
constructor(deps) {
deps.getDataFromServer("dataForWidget")
.then(data => this.render(data));
}
}
Estou muito desconfortável por atrapalhar a interface pública do Widget assim e expor os detalhes da implementação. Não vá.
b. Exponha as importações para permitir zombar delas.
Algo como:
import { getDataFromServer } from 'network.js';
export let deps = {
getDataFromServer
};
export class Widget() {
constructor() {
deps.getDataFromServer("dataForWidget")
.then(data => this.render(data));
}
}
então:
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();
});
});
Isso é menos invasivo, mas requer que eu escreva um monte de clichê para cada módulo, e ainda há o risco de eu usá-lo em getDataFromServer
vez de deps.getDataFromServer
o tempo todo. Estou desconfortável com isso, mas essa é a minha melhor ideia até agora.
createSpy
( github.com/jasmine/jasmine/blob/… ) com uma referência importada para getDataFromServer do módulo 'network.js'. Para que, no arquivo de testes do widget, você importasse getDataFromServer e, então,let spy = createSpy('getDataFromServer', getDataFromServer)
spyOn
naquele objeto, importado donetwork.js
módulo. É sempre uma referência ao mesmo objeto.Widget
a interface pública da?Widget
está bagunçado semdeps
. Por que não tornar a dependência explícita?Respostas:
Comecei a empregar o
import * as obj
estilo em meus testes, que importa todas as exportações de um módulo como propriedades de um objeto que pode ser zombado. Acho que isso é muito mais limpo do que usar algo como religar ou solicitar proxy ou qualquer técnica semelhante. Eu fiz isso com mais frequência ao precisar zombar de ações Redux, por exemplo. Aqui está o que eu poderia usar para o seu exemplo acima:Se a sua função for uma exportação padrão,
import * as network from './network'
ela produzirá{default: getDataFromServer}
e você poderá zombar de network.default.fonte
import * as obj
único no teste ou também no seu código regular?[method_name] is not declared writable or has no setter
que faz sentido, já que as importações da es6 são constantes. Existe uma maneira de contornar isso?import
(ao contráriorequire
, que pode ir a qualquer lugar) é içado, então você não pode importar tecnicamente várias vezes. Parece que seu espião está sendo chamado em outro lugar? Para evitar que os testes atrapalhem o estado (conhecido como poluição de teste), você pode redefinir seus espiões em um afterEach (por exemplo, sinon.sandbox). Jasmine, acredito que faça isso automaticamente.import
em seus JS não estão realmente usando os módulos do ES6. Algo como Webpack ou babel irá intervir em tempo de compilação e convertê-lo tanto em seu próprio mecanismo interno para chamar partes distantes do código (por exemplo__webpack_require__
) ou em um dos pré-ES6 de facto normas, commonjs, AMD ou UMD. E essa conversão geralmente não segue estritamente as especificações. Portanto, para muitos, muitos desenvolvedores agora, essa resposta funciona bem. Por enquanto.@carpeliam está correto, mas observe que, se você quiser espionar uma função em um módulo e usar outra função nesse módulo que chama essa função, precisará chamá-la como parte do espaço para nome das exportações, caso contrário, o espião não será usado.
Exemplo errado:
Exemplo certo:
fonte
exports.myfunc2
é uma referência direta amyfunc2
atéspyOn
substitui-lo por uma referência a uma função espião.spyOn
irá alterar o valor deexports.myfunc2
e substituí-lo com um objeto de espionagem, enquantomyfunc2
permanece intocado no escopo do módulo (porquespyOn
não tem acesso a ele)*
congelar o objeto e os atributos do objeto não podem ser alterados?export function
juntamenteexports.myfunc2
combina tecnicamente a sintaxe do módulo commonjs e ES6 e isso não é permitido nas versões mais recentes do webpack (2+) que requerem o uso da sintaxe do módulo ES6 do tipo tudo ou nada. Adicionei uma resposta abaixo com base nesta que funcionará em ambientes estritos do ES6.Eu implementei uma biblioteca que tenta resolver o problema da zombaria em tempo de execução das importações da classe Typescript sem precisar da classe original para saber sobre qualquer injeção explícita de dependência.
A biblioteca usa a
import * as
sintaxe e substitui o objeto exportado original por uma classe de stub. Ele retém a segurança de tipo, para que seus testes sejam interrompidos no tempo de compilação se um nome de método tiver sido atualizado sem a atualização do teste correspondente.Esta biblioteca pode ser encontrada aqui: ts-mock-imports .
fonte
A resposta da @ vdloo me levou na direção certa, mas o uso das palavras-chave "exportações" e do módulo ES6 "exportação" do commonjs juntos no mesmo arquivo não funcionou para mim (o webpack v2 ou posterior reclama). Em vez disso, estou usando uma exportação padrão (variável nomeada) agrupando todas as exportações de módulos nomeados individuais e importando a exportação padrão no meu arquivo de testes. Estou usando a seguinte configuração de exportação com mocha / sinon e o stubbing funciona bem sem a necessidade de religar, etc .:
fonte
let MyModule
não é necessário usar a exportação padrão (pode ser um objeto bruto). Além disso, esse método não precisamyfunc1()
ser chamadomyfunc2()
, funciona apenas para espioná-lo diretamente.Eu encontrei esta sintaxe para trabalhar:
Meu módulo:
Código de teste do meu módulo:
Veja o doc .
fonte
jest.mock()
deve corresponder ao nome usado em import / packge.json, em vez do nome da constante. Nos documentos que ambos são iguais, mas com um código comoimport jwt from 'jsonwebtoken'
você precisa configurar o mock comojest.mock('jsonwebtoken')
Eu ainda não tentei, mas acho que a zombaria pode funcionar. Ele permite que você substitua o módulo real por um mock que você forneceu. Abaixo está um exemplo para lhe dar uma idéia de como funciona:
Parece que
mockery
não é mais mantido e acho que funciona apenas com o Node.js, mas, mesmo assim, é uma solução interessante para zombar de módulos que, de outra forma, seriam difíceis de zombar.fonte