Controllori express test unità?

4

Sto usando meanjs per un progetto. Include un generatore di yeoman con alcuni test rapidi (model.test.js & routes.test.js)

I test fanno esattamente ciò che pubblicizzano. La mia domanda è però, dovrei scrivere anche test per i miei controller express?

Al momento i test dei percorsi assicurano che il server risponda con i dati appropriati nel suo complesso , ma dovrei scrivere più test granulari per ciascuna parte del mio controller come bene? Non vedo molti esempi di questo online ....

    
posta Justin Young 27.01.2016 - 07:18
fonte

1 risposta

3

TLDR

Ci sono due opzioni:

  1. Continua a eseguire il test nel suo complesso con test funzionali (poiché questi test as a whole non sono test unitari). Puoi sentire che questo non è il modo giusto, ma può essere l'unico modo per utilizzare il tuo codice attuale.
  2. Codice del refattore in unità più piccole (classi / funzioni) per rendere possibile il test dell'unità reale. Quindi crea dei test unitari per queste "unità" più piccole.

Spiegazione lunga

Innanzitutto, per essere sicuri che stiamo parlando dello stesso, l'esempio del codice del controller:

// Controller code
exports.changePassword = function (req, res, next) {
  // Init Variables
  var passwordDetails = req.body;
  var message = null;

  if (req.user) {
    if (passwordDetails.newPassword) {
      User.findById(req.user.id, function (err, user) {
        if (!err && user) {
          if (user.authenticate(passwordDetails.currentPassword)) {
            if (passwordDetails.newPassword === passwordDetails.verifyPassword) {
              user.password = passwordDetails.newPassword;

              user.save(function (err) {
                  ...
                  res.send({ message: 'Password changed successfully' });
              });
            } else {
              res.status(400).send({ message: 'Passwords do not match' });
            }
          } else { 
              res.status(400).send({ message: 'Current password is incorrect' }); 
          }
        } else {
          res.status(400).send({ message: 'User is not found' });
        }
      });
    } else {
      res.status(400).send({ message: 'Please provide a new password' });
    }
  } else {
    res.status(400).send({ message: 'User is not signed in' });
  }
};

E il codice di test:

// Change password
...
// agent performs the http request to the application
agent.post('/api/users/password')
  .send({
    newPassword: '1234567890Aa$', ...
  })
  .expect(200)
  .end(function (err, res) {
    ...
    res.body.message.should.equal('Password changed successfully');
    return done();
  });

Questo approccio funziona, ma questi test non sono in realtà test unitari, ma test funzionali che verificano il sistema come una scatola nera. Ciò significa che sono più lenti e meno granulari.

Quindi, come possiamo scrivere unit test per i controller? Con la struttura esistente può essere fatto in questo modo (questo non è un codice reale, solo per dimostrare l'idea):

var users = require('../controllers/users.server.controller');

describe('User Controller Unit Tests:', function () {

  describe('Change Password', function () {
    it('should change the password', function (done) {
      req = RequestMock({
          'body': {
              'newPassword': 'xxx', 'passwordVerify': 'xxx'
          }
      );
      res = ResponseMock();
      users.changePassword(req, res).then(function() {
        res.body.message.should.equal('Password changed successfully');
      });
    });

Ci avviciniamo al test unitario e ora abbiamo richieste / risposte e chiamiamo il nostro changePassword direttamente, ma lo testiamo ancora nel suo insieme. La struttura del test non è cambiata molto.

La soluzione è rendere il codice testabile per primo, ridimensionarlo per estrarre units dal codice changePassword , che ora fa troppo:

  • Ottieni i dati in arrivo dalla richiesta
  • Convalida i dati in arrivo
  • Modifica la password
  • Gestisci possibili errori
  • Formatta l'output
  • Invia l'output

La migliore struttura del codice potrebbe contenere i seguenti oggetti:

  • Controller: recupera i dati in arrivo dalla richiesta, formatta e invia l'output
  • NewPasswordConstraints - convalida / verifica i dati in arrivo
  • Utente - cambia la password

Il codice del controller potrebbe essere qualcosa del tipo:

// Controller code
exports.changePassword = function (req, res, next) {
  // Get the incoming data
  var passwordDetails = req.body;
  // Validate the incoming data
  NewPasswordConstraints(passwordDetails).validate().then(function(user) {
    // Actually change the password
    return user.set_new_password(passwordDetails.newPassword);
  }).then(function(user) {
    // Format and send the success response
    res.send({ message: 'Password changed successfully' }); 
    // Format and send the error response
  }).catch(function(error) {
    res.status(error.code).send({ message: error.message }); 
  });
}

Ancora una volta, questo è più uno pseudo-codice che un codice reale, ma penso che l'idea dovrebbe essere chiara.

Ora è possibile creare unit test per la business logic: NewPasswordConstraints e User classes.

Allo stesso tempo, il codice del controller diventa sottile e integra solo altri oggetti. Di solito non scrivo test unitari per questi controller "slim" e li lascio per i test funzionali.

    
risposta data 27.01.2016 - 11:23
fonte

Leggi altre domande sui tag