Na parte anterior, concentrei-me no fato de que a equipe que estou desenvolvendo implementa um comportamento que pode ser descrito com este teste:
it('execute should return promise', () => { request.configure(options); request.execute().then((result) => { expect(result.Id).toEqual(1); expect(result.Name).toEqual('Jack'); }); });
Parece-me agora que obter o Promise
como resultado e processá-lo não é exatamente o que eu gostaria. Seria melhor se a própria equipe executasse esse trabalho de rotina e o resultado fosse colocado, por exemplo, no repositório Redux
. Vou tentar reescrever o teste existente para expressar minhas novas 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'); }); });
Isso provavelmente é mais conveniente, apesar de agora eu ter que ensinar o método execute
da classe Request
a executar o método de retorno de chamada se o usuário o passar como argumento. Você não pode ficar sem isso, porque na execute
interna eu suponho usar chamadas assíncronas, cujos resultados do teste só podem ser testados se eles estiverem convencidos de que sua execução foi concluída.
Além disso ... Analisando a primeira linha de código, entendo que, antes de poder voltar a editar o código da classe Request
, preciso adicionar o pacote Redux
ao projeto, implementar pelo menos um
e empacotá-lo separadamente na Store
. O primeiro teste provavelmente será para a caixa de velocidades:
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); });
../../src/reducers/user
os testes e concordo com o Jasmine
que, além de todos os erros anteriores, não ../../src/reducers/user
encontrado um módulo com o nome ../../src/reducers/user
. Portanto, escreverei, especialmente porque promete ser pequeno e terrivelmente previsível:
const user = (state = null, action) => { switch (action.type) { case 'USER': return action.user; default: return state; } }; module.exports = user;
Eu executo testes e não vejo melhorias radicais. Isso ocorre porque o módulo ../../src/store
, cuja existência eu assumi no teste para minha classe Request
, ainda não foi implementado. E também não há teste para ele ainda. Vou começar, é claro, com o teste:
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); }); });
Testes? Existem mais relatórios sobre a falta do módulo de store
, por isso vou lidar com isso imediatamente.
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;
Entendendo que terei mais de uma caixa de velocidades, corro um pouco à frente na implementação do
e uso o método combineReducers
ao montá- combineReducers
. execute
os testes novamente e vejo uma nova mensagem de erro informando que o método execute
da minha classe Request
não funciona como sugere o meu teste. Como resultado do método execute, o registro do usuário não aparece no
. É hora de refatorar a classe Request
.
Lembro-me de como agora o teste do método execute
parece:
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'); }); });
E vou corrigir o código do método em si para que o teste tenha a chance de executar:
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(); }); }
Vou digitar no console de npm test
da npm test
e ... Bingo! Minha solicitação aprendeu não apenas a receber dados do banco de dados, mas também a salvá-los no
processo de processamento futuro, para que operações subsequentes pudessem receber esses dados sem problemas.
Mas! Meu manipulador pode enviar apenas um tipo de ação para o
, e isso limita muito seus recursos. Mas eu quero usar esse código repetidamente sempre que precisar extrair algum registro do banco de dados e enviá-lo para a célula do
para processamento adicional sob a chave necessária. E então começo a refatorar o teste novamente:
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'); }); });
Ocorreu-me que seria bom salvar a classe Request
de uma funcionalidade incomum para processar os resultados da consulta. Request
semântica é uma solicitação. Atendeu à solicitação, recebeu a resposta, a tarefa foi concluída, o princípio de responsabilidade exclusiva da classe é respeitado. E que alguém especialmente treinado nesse processo lide com os resultados, cuja única responsabilidade deve ser uma determinada versão do próprio processamento. Portanto, decidi transferir o método de success
para as configurações da solicitação, que é a tarefa de processar os dados retornados com êxito pela solicitação.
Testes, agora você não pode executar. Eu entendo isso intelectualmente. Não consertei nada no teste em si e não mudei nada na implementação, e os testes devem continuar sendo executados com êxito. Mas emocionalmente, preciso executar o comando npm test
e executá-lo, e npm test
editando a implementação do meu método execute
na classe Request
para substituir a linha pela chamada para store.dispatch(...)
pela linha com a chamada para 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(); }); }
Eu executo testes. Voila! Os testes são completamente verdes. A vida está melhorando! O que vem a seguir? Imediatamente vejo que você precisa alterar o título do teste, porque ele não corresponde à realidade. O teste não verifica se o envio do método ocorre como resultado da solicitação, mas se a consulta atualiza o estado no contêiner. Portanto, altero o título do teste para ... bem, por exemplo:
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'); }); });
O que vem a seguir? E então acho que é hora de prestar atenção, não quando, em vez dos dados solicitados, minha solicitação retornará um erro. Este não é um cenário tão impossível. Certo? E o principal é que, neste caso, não poderei preparar e enviar o conjunto de dados necessário para o meu operador KYC, por uma questão de integração com a qual estou escrevendo todo esse código. É isso mesmo? Então Primeiro vou escrever um teste:
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!'); }); });
Não sei se a estrutura do teste mostra que decidi economizar tempo e dinheiro e escrevi um mínimo de código para verificar se a solicitação retorna um erro? Não viu?
Não quero perder tempo codificando implementações adicionais do TableMock
, o que TableMock
erros. Decidi que no momento algumas construções condicionais na implementação existente seriam suficientes para mim e sugeri que isso pudesse ser ajustado por meio de parâmetros de consulta de query
. Então, minhas suposições são:
- Se o
Id
na consulta options.query
for 1 , minha pseudo-tabela sempre retornará a Promise
permitida com o primeiro registro da coleção. - Se o
Id
na consulta options.query
for 555 , minha pseudo-tabela sempre retornará uma Promise
rejeitada com uma instância de Error
dentro, cujo conteúdo da message
é Algo está errado! .
Claro, isso está longe de ser o ideal. Seria muito mais legível e conveniente para a percepção implementar as instâncias correspondentes do DbMock
, bem, por exemplo, HealthyDbMock
, FaultyDbMock
, EmptyDbMock
. Pelos nomes dos quais fica claro imediatamente que o primeiro sempre funcionará corretamente, o segundo sempre funcionará incorretamente e, como no terceiro, podemos assumir que sempre retornará null
vez do resultado. Talvez, depois de verificar minhas primeiras suposições da maneira mencionada acima, que me pareça levar um tempo mínimo, eu estarei DbMock
implementação de duas instâncias adicionais do DbMock
que simulam comportamentos prejudiciais .
Eu executo testes. Recebo o erro esperado da falta da propriedade necessária no
e ... estou escrevendo outro teste. Dessa vez, para uma caixa de velocidades que manipulará ações com o 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(); }); });
Executando os testes novamente. Tudo é esperado, mais um foi adicionado aos erros existentes. Eu percebo a
:
const error = (state = [], action) => { switch (action.type) { case 'ERROR': return state.concat([action.error]); default: return state; } }; module.exports = error;
Estou executando testes novamente. O novo redutor funciona conforme o esperado, mas ainda preciso garantir que ele se conecte ao repositório e processe as ações a que se destina. Portanto, estou escrevendo um teste adicional para o conjunto de testes de armazenamento 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(); });
Eu executo testes. Tudo é esperado. Uma ação com o tipo ERROR
não processa o armazenamento existente. Modificando o código de inicialização do repositório 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;
Pela centésima vez ele jogou uma rede ... Muito bem! O repositório agora acumula mensagens de erro recebidas em uma propriedade de contêiner separada.
Agora, apresentarei algumas construções condicionais na implementação existente do TableMock
, ensinando-a a iniciar algumas consultas dessa maneira, retornando um erro. O código atualizado é assim:
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;
execute
os testes e recebo uma mensagem sobre a rejeição não tratada de Promise
no método execute
da classe Request
. Eu adiciono o código ausente:
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(); }); }
E eu executo os testes novamente. E ??? Na verdade, não há teste para o método execute
da classe Request
, este:
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!'); }); });
Ele completou com sucesso. Portanto, a funcionalidade da consulta em termos de tratamento de erros pode ser considerada implementada. Outro teste caiu, um que verifica o repositório quanto à manipulação de erros. O problema é que meu módulo de implementação de armazenamento retorna a mesma instância de armazenamento estático para todos os consumidores em todos os testes. Nesse sentido, como o envio de erros já ocorre em dois testes, em um deles a verificação do número de erros no contêiner não passa necessariamente. Porque no momento em que o teste é iniciado, já existe um erro no contêiner e mais um é adicionado lá durante o lançamento do teste. Então, aqui está este código:
const error = store.getState().error; expect(error.length).toEqual(1);
Lança uma exceção, relatando que a expressão error.length
é realmente 2, não 1. Esse problema agora será resolvido simplesmente transferindo o código de inicialização do armazenamento diretamente para o código de inicialização do teste de armazenamento:
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(); }); });
O código de inicialização do teste agora parece um pouco inchado, mas posso retornar à refatoração posteriormente.
Eu executo testes. Voila! Todos os testes foram concluídos e você pode fazer uma pausa.