Search code examples
javascriptmocha.jsrefactoringchaidry

Refactoring to reduce repitition in Mocha API test


When running API tests in Node.js I often find myself repeating whole blocks of it statements with slightly different assertions. This seems a waste and I'd like it to respect DRY principles.

Let's take the following as an example:-

const { expect } = require('chai');
const got = require('got');
const core = require('../Libraries/CoreFunctions')


describe('Some description', function() {
  
  beforeEach(function() {
    
  })

    it('should do something', function(done) {
      got.get('https://someurl.com',
      {headers: core.headers()})
      .then(res => {
        core.respDateCode(res);
        console.log(core.prettyJSON(res));  
        expect(core.something).to.be.lessThan(2000);
        done()
      })
      .catch(err => {
        console.log('Error: ', err.message);
      });
    }).timeout(5000);
  
    

    it('should do something else', function(done) {
      got.get('https://someurl.com',
      {headers: core.headers()})
      .then(res => {
        core.respDateCode(res);
        console.log(core.prettyJSON(res));  
        expect(core.somethingElse).to.be.greaterThan(1000);
        done()
      })
      .catch(err => {
        console.log('Error: ', err.message);
      });
    }).timeout(5000);
  
  
  });
  

I'm looking for suggestions as to how best to refactor the above to reduce repetition?


Solution

  • Move the logic for fetching into a seperate file. You can then encapsulate every request into a function (with no parameters, so if API URL changes, your tests won't have to change). Every test should call the function under test explicitly in the "it" block, so it is quickly apparent what is being tested. If you have a lot of repeated setup code, you can move that into a function.

    A nice side effect of the isolation of the API calls is that you will end up with a client for the API, that is actually being tested at the same time as your API.

    Don't be afraid of your test code being duplicated at the high level. Basically "given this setup, when function under test is called, then this happens". You can put test setup into other functions, but don't overdo it, as you might risk not being able to tell what actually happened when looking at the test. Also, never abstract away the function under test.

    const { expect } = require('chai');
    const gotClient = require('../somewhere/client/uses/got');
    const core = require('../Libraries/CoreFunctions')
    
    describe('Some description', function() {
      
    
      it('should do something', async function(done) {
        // given
        const res = await gotClient.resource.getOne()
    
        // when
        core.functionThatIsBeingTested(res);
    
        // then
        expect(core.something).to.be.lessThan(2000);
        done()
      }).timeout(5000);
    
      it('should do something else', async function(done) {
        // given
        const res = await gotClient.resource.getOne()
        
        // when
        core.functionThatIsBeingTested(res);
        
        // then
        console.log(core.prettyJSON(res));  
        expect(core.somethingElse).to.be.greaterThan(1000);
        done()
      }).timeout(5000);
    });
    

    Notice, the only real difference between this version and your version is that in my version you don't need to concern yourself with the url and the headers, which makes the code more readable and easier to understand. It would be even better if client was named after the API it was fetching and the resource was a name of the resource.

    Just as an example:

    const res = await twilio.phoneNumbers.availableForPurchase()