Dans la partie précédente, je me suis concentré sur le fait que l'équipe que je développe implémente un comportement qui peut être décrit avec ce test:
it('execute should return promise', () => { request.configure(options); request.execute().then((result) => { expect(result.Id).toEqual(1); expect(result.Name).toEqual('Jack'); }); });
Il me semble maintenant que l'obtention de Promise
et son traitement ne correspondent pas exactement à ce que j'aimerais. Il serait préférable que l'équipe elle-même effectue ce travail de routine, et le résultat serait placé, par exemple, dans le référentiel Redux
. Je vais essayer de réécrire le test existant afin d'y exprimer mes nouvelles attentes:
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'); }); });
C'est probablement plus pratique, malgré le fait que je dois maintenant enseigner la méthode d' execute
de la classe Request
pour exécuter la méthode de rappel si l'utilisateur la passe en argument. Vous ne pouvez pas vous en passer, car à l'intérieur de execute
je suppose utiliser des appels asynchrones, dont les résultats de test ne peuvent être testés que s'ils sont convaincus que leur exécution est terminée.
De plus ... En regardant la première ligne de code, je comprends qu'avant de pouvoir revenir à l'édition du code de la classe Request
, je dois ajouter le package Redux
au projet, implémenter au moins un
et empaqueter séparément ce réducteur dans le Store
. Le premier test sera probablement pour la boîte de vitesses:
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); });
Je lance les tests et je suis d'accord avec Jasmine
qu'en plus de toutes les erreurs précédentes, un module avec le nom ../../src/reducers/user
pas ../../src/reducers/user
trouvé. Je vais donc l'écrire, d'autant plus qu'il promet d'être minuscule et terriblement prévisible:
const user = (state = null, action) => { switch (action.type) { case 'USER': return action.user; default: return state; } }; module.exports = user;
Je lance des tests et ne vois pas d'améliorations radicales. C'est parce que le module ../../src/store
, dont j'ai supposé l'existence dans le test pour ma classe Request
, je ne l'ai pas encore implémenté. Et il n'y a pas encore de test pour lui. Je vais bien sûr commencer par le test:
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); }); });
Des tests? Il y a plus de rapports sur le manque du module de store
, donc je vais m'en occuper immédiatement.
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;
Comprenant que j'aurai plus d'une boîte de vitesses, je suis un peu en avance dans l'implémentation du
et j'utilise la méthode combineReducers
lors de son assemblage. J'exécute à nouveau les tests et je vois un nouveau message d'erreur qui m'indique que la méthode d' execute
de ma classe Request
ne fonctionne pas comme le suggère mon test. En raison de la méthode d'exécution, l'enregistrement utilisateur n'apparaît pas dans le
. Il est temps de refactoriser la classe Request
.
Je me rappelle à quoi ressemble maintenant le test de la méthode d' 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'); }); });
Et je vais corriger le code de la méthode elle-même afin que le test puisse s'exécuter:
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(); }); }
Je vais taper dans la console de npm test
et ... Bingo! Ma demande a appris non seulement à recevoir des données de la base de données, mais aussi à les enregistrer dans le
futur processus de traitement, afin que les opérations suivantes puissent recevoir ces données sans problème.
Mais! Mon gestionnaire ne peut envoyer qu'un seul type d'action dans le
, ce qui limite considérablement ses capacités. Mais je veux utiliser ce code à plusieurs reprises chaque fois que j'ai besoin d'extraire des enregistrements de la base de données et de les envoyer à la cellule de
pour un traitement ultérieur sous la clé dont j'ai besoin. Et donc je recommence à refactoriser le test:
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'); }); });
Il m'est venu à l'esprit qu'il serait bien de sauvegarder la classe Request
d'une fonctionnalité inhabituelle pour le traitement des résultats de requête. Sémantiquement, Request
est une requête. Satisfait à la demande, reçu la réponse, la tâche est terminée, le principe de la seule responsabilité du cours est respecté. Et laissez quelqu'un spécialement formé à ce processus gérer les résultats, dont la seule responsabilité est censée être une certaine version du traitement lui-même. Par conséquent, j'ai décidé de transférer la méthode de success
aux paramètres de demande, qui est la tâche de traiter les données renvoyées avec succès par la demande.
Tests, maintenant vous ne pouvez pas exécuter. Je comprends cela intellectuellement. Je n'ai rien corrigé dans le test lui-même et je n'ai rien changé dans l'implémentation, et les tests devraient continuer à s'exécuter avec succès. Mais émotionnellement, j'ai besoin d'exécuter la commande de npm test
et je l'exécute, et je procède à modifier l'implémentation de ma méthode d' execute
dans la classe Request
pour remplacer la ligne par l'appel à store.dispatch(...)
par la ligne avec l'appel à 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(); }); }
Je lance des tests. Voila! Les tests sont complètement verts. La vie s'améliore! Et ensuite? Je vois immédiatement que vous devez changer le titre du test, car il ne correspond pas tout à fait à la réalité. Le test ne vérifie pas que la répartition de la méthode se produit à la suite de la demande, mais que la requête met à jour l'état dans le conteneur. Par conséquent, je change le titre du test en ... eh bien, par exemple:
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'); }); });
Et ensuite? Et puis je pense qu'il est temps de faire attention et non un cas où, au lieu des données demandées, ma demande renverra une erreur. Ce n'est pas un scénario aussi impossible. Non? Et l'essentiel est que dans ce cas, je ne pourrai pas préparer et envoyer le jeu de données requis à mon opérateur KYC, dans un souci d'intégration avec lequel j'écris tout ce code. En est-il ainsi? Alors Je vais d'abord écrire un test:
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!'); }); });
Je ne sais pas si la structure du test montre que j'ai décidé de gagner du temps et de l'argent et d'écrire un minimum de code pour vérifier si la demande renvoie une erreur? Vu non?
Je ne veux pas perdre de temps à coder des implémentations supplémentaires de TableMock
, ce qui imitera les erreurs. J'ai décidé que pour le moment quelques constructions conditionnelles dans l'implémentation existante me suffiraient, et j'ai suggéré que cela puisse être ajusté via query
paramètres de requête de query
. Donc, mes hypothèses sont les suivantes:
- Si l'
Id
dans la requête options.query
est 1 , ma pseudo-table renvoie toujours la Promise
autorisée avec le tout premier enregistrement de la collection. - Si l'
Id
dans la requête options.query
est 555 , ma pseudo-table renvoie toujours une Promise
rejetée avec une instance d' Error
intérieur, dont le contenu du message
est Quelque chose ne va pas! .
Bien sûr, c'est loin d'être idéal. Il serait beaucoup plus lisible et pratique pour la perception d'implémenter les instances DbMock
correspondantes, par exemple, HealthyDbMock
, FaultyDbMock
, EmptyDbMock
. D'après les noms dont il est immédiatement clair que le premier fonctionnera toujours correctement, le second fonctionnera toujours incorrectement, et comme pour le troisième, nous pouvons supposer qu'il retournera toujours null
au lieu du résultat. Peut-être, après avoir vérifié mes premières hypothèses de la manière susmentionnée, qu'il me semble que cela prend un minimum de temps, je serai DbMock
mise DbMock
œuvre de deux instances supplémentaires de DbMock
qui simulent un comportement malsain .
Je lance des tests. J'obtiens l'erreur attendue du manque de la propriété dont j'ai besoin dans le
et ... J'écris un autre test. Cette fois, pour une boîte de vitesses qui gérera les actions de type 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(); }); });
Relancer les tests. Tout est attendu, un de plus a été ajouté aux erreurs existantes. Je réalise la
:
const error = (state = [], action) => { switch (action.type) { case 'ERROR': return state.concat([action.error]); default: return state; } }; module.exports = error;
Je refais des tests. Le nouveau réducteur fonctionne comme prévu, mais je dois encore m'assurer qu'il se connecte au référentiel et traite les actions auxquelles il est destiné. Par conséquent, j'écris un test supplémentaire pour la suite de tests de stockage existante:
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(); });
Je lance des tests. Tout est attendu. Une action de type ERROR
ne traite pas le stockage existant. Modification du code d'initialisation du référentiel existant:
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;
Pour la centième fois, il a lancé un filet ... Très bien! Le référentiel accumule désormais les messages d'erreur reçus dans une propriété de conteneur distincte.
Je vais maintenant introduire quelques constructions conditionnelles dans l'implémentation TableMock
existante, en lui apprenant à lancer certaines requêtes de cette manière, en renvoyant une erreur. Le code mis à jour ressemble à ceci:
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;
J'exécute les tests et reçois un message sur le rejet non géré de Promise
dans la méthode d' execute
de la classe Request
. J'ajoute le code manquant:
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(); }); }
Et je relance les tests. Et ??? Il n'y a en fait aucun test pour la méthode d' execute
de la classe Request
, celle-ci:
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!'); }); });
Il a terminé avec succès. Ainsi, la fonctionnalité de requête en termes de gestion des erreurs peut être considérée comme implémentée. Un autre test a échoué, celui qui vérifie le référentiel pour la gestion des erreurs. Le problème est que mon module d'implémentation de stockage renvoie la même instance de stockage statique à tous les consommateurs dans tous les tests. À cet égard, étant donné que l'envoi d'erreurs se produit déjà dans deux tests, dans l'un d'eux, la vérification du nombre d'erreurs dans le conteneur ne passe pas nécessairement. Parce qu'au moment où le test est démarré, il y a déjà une erreur dans le conteneur et une autre y est ajoutée lors du lancement du test. Voici donc ce code:
const error = store.getState().error; expect(error.length).toEqual(1);
Lève une exception, signalant que l'expression error.length
est en fait 2, et non 1. Ce problème pour le moment, je le résoudrai simplement en transférant le code d'initialisation du stockage directement dans le code d'initialisation du test de stockage:
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(); }); });
Le code d'initialisation du test semble maintenant un peu gonflé, mais je pourrai revenir plus tard sur sa refactorisation.
Je lance des tests. Voila! Tous les tests sont terminés et vous pouvez faire une pause.