Desarrollar un equipo para solicitar datos de una base de datos - parte 2

En la parte anterior, me concentré en el hecho de que el equipo que estoy desarrollando implementa un comportamiento que se puede describir con esta prueba:


it('execute should return promise', () => { request.configure(options); request.execute().then((result) => { expect(result.Id).toEqual(1); expect(result.Name).toEqual('Jack'); }); }); 

Ahora me parece que obtener Promise como resultado y procesarlo no es exactamente lo que me gustaría. Sería mejor si el equipo mismo realizara este trabajo de rutina, y el resultado se colocaría, por ejemplo, en el repositorio de Redux . Intentaré reescribir la prueba existente para expresar mis nuevas expectativas:


 const store = require('../../src/store'); const DbMock = require('../mocks/DbMock'); const db = new DbMock(); const Request = require('../../src/storage/Request'); const options = { tableName: 'users', query: { Id: 1 } }; let request = null; beforeEach(() => { request = new Request(db, store); }); it('should dispatch action if record exists', () => { request.configure(options); request.execute(() => { const user = store.getState().user; expect(user.Id).toEqual(options.query.Id); expect(user.Name).toEqual('Jack'); }); }); 

Esto es probablemente más conveniente, a pesar del hecho de que ahora tengo que enseñar el método de execute de la clase Request para ejecutar el método de devolución de llamada si el usuario lo pasa como argumento. No puede prescindir de él, porque dentro de la execute supongo que usaré llamadas asíncronas, cuyos resultados de prueba solo se pueden probar si están convencidos de que su ejecución se ha completado.


Además ... Mirando la primera línea de código, entiendo que antes de poder volver a editar el código para la clase Request , necesito agregar el paquete Redux al proyecto, implementar al menos un y separado en la Store . La primera prueba probablemente será para la caja de cambios:


 const reduce = require('../../src/reducers/user'); it('should return new state', () => { const user = { Id: 1, Name: 'Jack'}; const state = reduce(null, {type: 'USER', user }); expect(state).toEqual(user); }); 

Ejecuté las pruebas y estoy de acuerdo con Jasmine en que, además de todos los errores anteriores, no se encontró un módulo con el nombre ../../src/reducers/user . Por lo tanto, lo escribiré, especialmente porque promete ser pequeño y terriblemente predecible:


 const user = (state = null, action) => { switch (action.type) { case 'USER': return action.user; default: return state; } }; module.exports = user; 

Realizo pruebas y no veo mejoras radicales. Esto se debe a que el módulo ../../src/store , ../../src/store existencia asumí en la prueba para mi clase Request , aún no lo he implementado. Y tampoco hay una prueba para él todavía. Comenzaré por supuesto con la prueba:


 describe('store', () => { const store = require('../src/store'); it('should reduce USER', () => { const user = { Id: 1, Name: 'Jack' }; store.dispatch({type: 'USER', user }); const state = store.getState(); expect(state.user).toEqual(user); }); }); 

Pruebas? Hay más informes sobre la falta del módulo de la store , por lo que me ocuparé de él de inmediato.


 const createStore = require('redux').createStore; const combineReducers = require('redux').combineReducers; const user = require('./reducers/user'); const reducers = combineReducers({user}); const store = createStore(reducers); module.exports = store; 

Comprendiendo que tendré más de una caja de cambios, me adelanto un poco en la implementación del y uso el método combineReducers cuando combineReducers . Ejecuto las pruebas nuevamente y veo un nuevo mensaje de error que me dice que el método de execute de mi clase Request no funciona como lo sugiere mi prueba. Como resultado del método de ejecución, el registro de usuario no aparece en el . Es hora de refactorizar la clase Request .


Recuerdo cómo ahora se ve la prueba del método execute :


 it('should dispatch action if record exists', () => { request.configure(options); request.execute(() => { const user = store.getState().user; expect(user.Id).toEqual(options.query.Id); expect(user.Name).toEqual('Jack'); }); }); 

Y corregiré el código del método en sí para que la prueba tenga la oportunidad de ejecutarse:


 execute(callback){ const table = this.db.Model.extend({ tableName: this.options.tableName }); table.where(this.options.query).fetch().then((item) => { this.store.dispatch({ type: 'USER', user: item }); if(typeof callback === 'function') callback(); }); } 

npm test consola de npm test y ... ¡Bingo! Mi solicitud aprendió no solo a recibir datos de la base de datos, sino también a guardarlos en el proceso de procesamiento futuro, para que las operaciones posteriores pudieran recibir estos datos sin problemas.


Pero! Mi controlador puede enviar solo un tipo de acción al , y esto limita enormemente sus capacidades. Pero quiero usar este código repetidamente cada vez que necesite extraer algún registro de la base de datos y enviarlo a la celda del para su posterior procesamiento bajo la clave que necesito. Y así empiezo a refactorizar la prueba nuevamente:


 const options = { tableName: 'users', query: { Id : 1 }, success (result, store) { const type = 'USER'; const action = { type , user: result }; store.dispatch(action); } }; it('should dispatch action if record exists', () => { request.configure(options); request.execute(() => { const user = store.getState().user; expect(user.Id).toEqual(options.query.Id); expect(user.Name).toEqual('Jack'); }); }); 

Se me ocurrió que sería bueno guardar la clase Request de una funcionalidad inusual para procesar los resultados de la consulta. Semánticamente Request es una solicitud. Cumplió la solicitud, recibió la respuesta, se completó la tarea, se respeta el principio de responsabilidad exclusiva de la clase. Y deje que alguien especialmente capacitado en este proceso maneje los resultados, cuya única responsabilidad se supone que es una cierta versión del procesamiento en sí. Por lo tanto, decidí transferir el método de success a la configuración de la solicitud, que es la tarea de procesar los datos devueltos con éxito por la solicitud.


Pruebas, ahora no puedes correr. Lo entiendo intelectualmente. No arreglé nada en la prueba en sí y no cambié nada en la implementación, y las pruebas deberían continuar ejecutándose con éxito. Pero emocionalmente, necesito ejecutar el npm test y lo ejecuto, y procedo a editar la implementación de mi método de execute en la clase Request para reemplazar la línea con la llamada a store.dispatch(...) con la línea con la llamada a this.options.success(...) :


 execute(callback){ const table = this.db.Model.extend({ tableName: this.options.tableName }); table.where(this.options.query).fetch().then((item) => { this.options.success(item, this.store); if(typeof callback !== 'undefined') callback(); }); } 

Corro pruebas. Voila! Las pruebas son completamente verdes. ¡La vida está mejorando! Que sigue Inmediatamente veo que necesitas cambiar el título de la prueba, porque no se corresponde con la realidad. La prueba no comprueba que el envío del método se produce como resultado de la solicitud, sino que la consulta actualiza el estado en el contenedor. Por lo tanto, cambio el título de la prueba a ... bueno, por ejemplo:


 it('should update store user state if record exists', () => { request.configure(options); request.execute(() => { const user = store.getState().user; expect(user.Id).toEqual(options.query.Id); expect(user.Name).toEqual('Jack'); }); }); 

Que sigue Y luego creo que es hora de prestar atención, no un caso cuando, en lugar de los datos solicitados, mi solicitud devolverá un error. Este no es un escenario tan imposible. Derecho? Y lo principal es que, en este caso, no podré preparar y enviar el conjunto de datos requerido a mi operador KYC, en aras de la integración con la que estoy escribiendo todo este código. Es asi? Entonces Primero escribiré una prueba:


 it('should add item to store error state', () => { options.query = { Id: 555 }; options.error = (error, store) => { const type = 'ERROR'; const action = { type, error }; store.dispatch(action); }; request.configure(options); request.execute(() => { const error = store.getState().error; expect(Array.isArray(error)).toBeTruthy(); expect(error.length).toEqual(1); expect(error[0].message).toEqual('Something goes wrong!'); }); }); 

No sé si la estructura de la prueba muestra que decidí ahorrar tiempo y dinero y escribir un mínimo de código para verificar si la solicitud devuelve un error. Visto no?


No quiero perder el tiempo codificando implementaciones adicionales de TableMock , que imitarán errores. Decidí que en este momento un par de construcciones condicionales en la implementación existente serían suficientes para mí, y sugerí que esto se puede ajustar a través de parámetros de consulta de query . Entonces mis suposiciones son:


  • Si el Id en la consulta options.query es 1 , entonces mi pseudo-tabla siempre devuelve la Promise permitida con el primer registro de la colección.
  • Si el Id en la consulta options.query es 555 , entonces mi pseudo-tabla siempre devuelve una Promise rechazada con una instancia de Error dentro, cuyo contenido del message es ¡ Algo sale mal! .

Por supuesto, esto está lejos de ser ideal. Sería mucho más legible y conveniente para la percepción implementar las instancias DbMock correspondientes, bueno, por ejemplo, HealthyDbMock , FaultyDbMock , EmptyDbMock . A partir de los nombres de los cuales queda claro de inmediato que el primero siempre funcionará correctamente, el segundo siempre funcionará incorrectamente y, en cuanto al tercero, podemos suponer que siempre devolverá null lugar del resultado. Quizás, después de verificar mis primeras suposiciones de la manera mencionada anteriormente, que me parece tomar un mínimo de tiempo, me DbMock implementación de dos instancias adicionales de DbMock que simulan un comportamiento poco saludable .


Corro pruebas. Recibo el error esperado de la falta de la propiedad que necesito en el y ... Estoy escribiendo otra prueba. Esta vez para una caja de cambios que manejará acciones con tipo ERROR .


 describe('error', () => { const reduce = require('../../src/reducers/error'); it('should add error to state array', () => { const type = 'ERROR'; const error = new Error('Oooops!'); const state = reduce(undefined, { type, error }); expect(Array.isArray(state)).toBeTruthy(); expect(state.length).toEqual(1); expect(state.includes(error)).toBeTruthy(); }); }); 

Ejecutando las pruebas nuevamente. Todo se espera, se ha agregado uno más a los errores existentes. Me doy cuenta de la :


 const error = (state = [], action) => { switch (action.type) { case 'ERROR': return state.concat([action.error]); default: return state; } }; module.exports = error; 

Estoy haciendo pruebas nuevamente. El nuevo reductor funciona como se esperaba, pero aún necesito asegurarme de que se conecta al repositorio y procesa las acciones para las que está destinado. Por lo tanto, estoy escribiendo una prueba adicional para el conjunto de pruebas de almacenamiento existente:


 it('should reduce error', () => { const type = 'ERROR'; const error = new Error('Oooops!'); store.dispatch({ type, error }); const state = store.getState(); expect(Array.isArray(state.error)).toBeTruthy(); expect(state.error.length).toEqual(1); expect(state.error.includes(error)).toBeTruthy(); }); 

Corro pruebas. Todo se espera. Una acción con tipo ERROR no procesa el almacenamiento existente. Modificación del código de inicialización del repositorio existente:


 const createStore = require('redux').createStore; const combineReducers = require('redux').combineReducers; const user = require('./reducers/user'); const error = require('./reducers/error'); const reducers = combineReducers({ error, user }); const store = createStore(reducers); module.exports = store; 

Por enésima vez arrojó una red ... ¡Muy bien! El repositorio ahora acumula los mensajes de error recibidos en una propiedad de contenedor separada.


Ahora introduciré un par de construcciones condicionales en la implementación de TableMock existente, enseñándole a iniciar algunas consultas de esta manera, devolviendo un error. El código actualizado se ve así:


 class TableMock { constructor(array){ this.container = array; } where(query){ this.query = query; return this; } fetch(){ return new Promise((resolve, reject) => { if(this.query.Id === 1) return resolve(this.container[0]); if(this.query.Id === 555) return reject(new Error('Something goes wrong!')); }); } } module.exports = TableMock; 

Ejecuto las pruebas y recibo un mensaje sobre el rechazo no controlado de Promise en el método de execute de la clase Request . Añado el código que falta:


 execute(callback){ const table = this.db.Model.extend({ tableName: this.options.tableName }); table.where(this.options.query).fetch().then((item) => { this.options.success(item, this.store); if(typeof callback === 'function') callback(); }).catch((error) => { this.options.error(error, this.store); if(typeof callback === 'function') callback(); }); } 

Y corro las pruebas nuevamente. Y ??? En realidad, no hay prueba para el método de execute de la clase Request , esta:


 it('should add item to store error state', () => { options.query = { Id: 555 }; options.error = (error, store) => { const type = 'ERROR'; const action = { type, error }; store.dispatch(action); }; request.configure(options); request.execute(() => { const error = store.getState().error; expect(Array.isArray(error)).toBeTruthy(); expect(error.length).toEqual(1); expect(error[0].message).toEqual('Something goes wrong!'); }); }); 

Él completó con éxito. Por lo tanto, la funcionalidad de consulta en términos de manejo de errores puede considerarse implementada. Otra prueba cayó, una que verifica el repositorio para el manejo de errores. El problema es que mi módulo de implementación de almacenamiento devuelve la misma instancia de almacenamiento estático a todos los consumidores en todas las pruebas. En este sentido, dado que el envío de errores ya se produce en dos pruebas, en una de ellas la verificación del número de errores en el contenedor no necesariamente pasa. Porque para cuando se inicia la prueba, ya hay un error en el contenedor y se agrega uno más durante el inicio de la prueba. Entonces aquí está este código:


 const error = store.getState().error; expect(error.length).toEqual(1); 

Lanza una excepción, informando que la expresión error.length es en realidad 2, no 1. Este problema ahora lo resolveré simplemente transfiriendo el código de inicialización de almacenamiento directamente al código de inicialización de prueba de almacenamiento:


 describe('store', () => { const createStore = require('redux').createStore; const combineReducers = require('redux').combineReducers; const user = require('../src/reducers/user'); const error = require('../src/reducers/error'); const reducers = combineReducers({ error, user }); const store = createStore(reducers); it('should reduce USER', () => { const user = { Id: 1, Name: 'Jack' }; store.dispatch({type: 'USER', user }); const state = store.getState(); expect(state.user).toEqual(user); }); it('should reduce error', () => { const type = 'ERROR'; const error = new Error('Oooops!'); store.dispatch({ type, error }); const state = store.getState(); expect(Array.isArray(state.error)).toBeTruthy(); expect(state.error.length).toEqual(1); expect(state.error.includes(error)).toBeTruthy(); }); }); 

El código de inicialización para la prueba ahora parece un poco hinchado, pero puedo volver a su refactorización más adelante.


Corro pruebas. Voila! Todas las pruebas se han completado y puede tomar un descanso.

Source: https://habr.com/ru/post/es436348/


All Articles