Tengo los siguientes 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() {
...
}
}
Estoy buscando una forma de probar Widget con una instancia simulada de getDataFromServer
. Si usara <script>
s separados en lugar de módulos ES6, como en Karma, podría escribir mi prueba 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();
});
});
Sin embargo, si estoy probando módulos ES6 individualmente fuera de un navegador (como con Mocha + babel), escribiría 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();
});
});
Está bien, pero ahora getDataFromServer
no está disponible en window
(bueno, no hay ninguno window
), y no sé cómo inyectar cosas directamente en widget.js
el ámbito de aplicación.
Entonces, ¿a dónde voy desde aquí?
- ¿Hay alguna manera de acceder al alcance de
widget.js
, o al menos reemplazar sus importaciones con mi propio código? - Si no, ¿cómo puedo hacer
Widget
comprobable?
Cosas que consideré:
a. Inyección manual de dependencias.
Elimine todas las importaciones widget.js
y espere que la persona que llama proporcione los departamentos.
export class Widget() {
constructor(deps) {
deps.getDataFromServer("dataForWidget")
.then(data => this.render(data));
}
}
Me incomoda mucho estropear la interfaz pública de Widget como esta y exponer los detalles de implementación. No vayas.
si. Exponer las importaciones para permitir burlarse de ellos.
Algo como:
import { getDataFromServer } from 'network.js';
export let deps = {
getDataFromServer
};
export class Widget() {
constructor() {
deps.getDataFromServer("dataForWidget")
.then(data => this.render(data));
}
}
luego:
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();
});
});
Esto es menos invasivo, pero requiere que escriba mucho repetitivo para cada módulo, y todavía existe el riesgo de que lo use en getDataFromServer
lugar de deps.getDataFromServer
todo el tiempo. Estoy inquieto al respecto, pero esa es mi mejor idea hasta ahora.
createSpy
( github.com/jasmine/jasmine/blob/… ) con una referencia importada a getDataFromServer del módulo 'network.js'. De modo que, en el archivo de pruebas del widget, importaría getDataFromServer y luego lo haríalet spy = createSpy('getDataFromServer', getDataFromServer)
spyOn
en ese objeto, importado denetwork.js
módulo. Siempre es una referencia al mismo objeto.Widget
la interfaz pública?Widget
está en mal estado sindeps
. ¿Por qué no hacer explícita la dependencia?Respuestas:
Comencé a emplear el
import * as obj
estilo dentro de mis pruebas, que importa todas las exportaciones de un módulo como propiedades de un objeto que luego se puede burlar. Creo que esto es mucho más limpio que usar algo como recablear o proxyquire o cualquier técnica similar. Lo he hecho con mayor frecuencia cuando necesito burlarme de las acciones de Redux, por ejemplo. Esto es lo que podría usar para su ejemplo anterior:Si su función resulta ser una exportación predeterminada, entonces
import * as network from './network'
produciría{default: getDataFromServer}
y puede burlarse de network.default.fuente
import * as obj
único en la prueba o también en su código regular?[method_name] is not declared writable or has no setter
que tiene sentido ya que las importaciones de es6 son constantes. ¿Hay alguna forma de solución?import
(a diferencia derequire
, que puede ir a cualquier parte) se levanta, por lo que técnicamente no puede importar varias veces. ¿Suena como si tu espía fuera llamado a otra parte? Para evitar que las pruebas se confundan (conocido como contaminación de prueba), puede restablecer sus espías en un AfterEach (por ejemplo, sinon.sandbox). Jasmine creo que hace esto automáticamente.import
en su JS realmente no usan módulos ES6. Algo como webpack o babel intervendrá en el momento de la construcción y lo convertirá en su propio mecanismo interno para llamar a partes distantes del código (por ejemplo__webpack_require__
) o en uno de los estándares de facto anteriores a ES6 , CommonJS, AMD o UMD. Y esa conversión a menudo no se adhiere estrictamente a las especificaciones. Entonces, para muchos, muchos desarrolladores en este momento, esta respuesta funciona bien. Por ahora.@carpeliam es correcto, pero tenga en cuenta que si desea espiar una función en un módulo y usar otra función en ese módulo que llama a esa función, debe llamar a esa función como parte del espacio de nombres de exportaciones; de lo contrario, el espía no se usará.
Ejemplo equivocado:
Ejemplo correcto:
fuente
exports.myfunc2
es una referencia directamyfunc2
hasta que lospyOn
reemplaza con una referencia a una función de espía.spyOn
cambiará el valor deexports.myfunc2
y lo reemplazará con un objeto espía, mientras quemyfunc2
permanece intacto en el alcance del módulo (porquespyOn
no tiene acceso a él)*
congelar el objeto y los atributos del objeto no se pueden cambiar?export function
junto conexports.myfunc2
está técnicamente mezclando la sintaxis del módulo commonjs y ES6 y esto no está permitido en las versiones más recientes de webpack (2+) que requieren el uso de la sintaxis del módulo ES6 todo o nada. Agregué una respuesta a continuación basada en esta que funcionará en entornos estrictos de ES6.Implementé una biblioteca que intenta resolver el problema de la burla en tiempo de ejecución de las importaciones de la clase Typecript sin necesidad de que la clase original conozca ninguna inyección explícita de dependencia.
La biblioteca usa la
import * as
sintaxis y luego reemplaza el objeto exportado original con una clase stub. Conserva la seguridad de tipo, por lo que sus pruebas se interrumpirán en el momento de la compilación si se actualizó el nombre de un método sin actualizar la prueba correspondiente.Esta biblioteca se puede encontrar aquí: ts-mock-imports .
fuente
La respuesta de @ vdloo me llevó en la dirección correcta, pero usar las palabras clave "exportar" del módulo ES6 "exportar" en el mismo archivo no funcionó para mí (webpack v2 o posterior se queja). En cambio, estoy usando una exportación predeterminada (variable con nombre) que envuelve todas las exportaciones individuales de módulos con nombre y luego importo la exportación predeterminada en mi archivo de pruebas. Estoy usando la siguiente configuración de exportación con mocha / sinon y el stubbing funciona bien sin necesidad de volver a cablear, etc.
fuente
let MyModule
no es necesario usar la exportación predeterminada (puede ser un objeto sin formato). Además, este método no requieremyfunc1()
llamarmyfunc2()
, solo funciona para espiarlo directamente.He encontrado que esta sintaxis funciona:
Mi modulo:
Código de prueba de mi módulo:
Ver el doc .
fuente
jest.mock()
tiene que coincidir con el nombre utilizado en import / packge.json en lugar del nombre de constante. En los documentos, ambos son iguales, pero con un código como elimport jwt from 'jsonwebtoken'
que necesita configurar el simulacro comojest.mock('jsonwebtoken')
No lo he intentado yo mismo, pero creo que la burla podría funcionar. Le permite sustituir el módulo real con un simulacro que haya proporcionado. A continuación se muestra un ejemplo para darle una idea de cómo funciona:
Parece que
mockery
ya no se mantiene y creo que solo funciona con Node.js, pero no obstante, es una buena solución para burlarse de módulos que de otro modo serían difíciles de burlar.fuente