1
\$\begingroup\$

I have been making an effort to add a fair amount of unit testing into my code. However, I find myself going against the DRY principle quite a bit.

I am currently running tests on my API with my tests in the following folder structure:

/user
  user.model.js
  user.test.js
  ....
/blog
  blog.model.js
  blog.test.js
....

Ive been using wildcards in my script test path to target all my test files nodemon --exec 'mocha ../api/resources/**/*.test.js'

I have noticed all my test files have a lot of stuff to require. Most of which is the same in each of the test files, example:

const chai = require('chai')
const chaiHttp = require('chai-http')
const { expect } = chai
const app = require('../../../app')
const { dropDb } = require('../../../test/helpers')

const mongoose = require('mongoose')
const User = mongoose.model('user')

chai.use(chaiHttp)

And my tests have a lot of duplicate 'expect' statements

it('should return error when no user email found', async () => {
    const result = await chai.request(app)
        .post('/api/user/login')
        .send({ email: 'fail@email.com', password: currentUserData.password })

    *expect(result).to.have.status(401)
    expect(result.error).to.exist
    expect(result.error.text).to.contain('No user found')*
})

it('should return error when password is incorrect', async () => {
    const result = await chai.request(app)
        .post('/api/user/login')
        .send({ email: currentUserData.email, password: '123456' })

    *expect(result).to.have.status(401)
    expect(result.error).to.exist
    expect(result.error.text).to.contain('Incorrect password')*
})

As you can see from the above tests the expect statements are basically the same minus the text. I have a lot of other tests where I have expects that check to make sure its JSON data or 200 before the more specific expects.

To me it feels as if I am repeating myself quite a bit on the requires and the expect statements. Is this standard practice or could I create some sort of higher order function to run the tests without having to require all the time.

Additionally, can I reuse a function with similar expect statements in it?

\$\endgroup\$
1
\$\begingroup\$

One of the recommended techniques for tests is refactor into an assert method. Duplications drive into fragile tests.

You should also try to create factory methods for the SUT.

it('should return error when password is incorrect', async () => {
    // Arrange
    const aValidEmail = currentUserData.email
    const aWrongPassword = '123456'

    // Act
    const result = await makeLoginRequest(aValidEmail, aWrongPassword)

    // Assert
    expectErrorResult(401, 'Incorrect password')
})

EDIT: In the end, makeLoginRequest() is not a factory method, but it could be the equivalent for this type of SUT. What we are trying to achieve here is avoiding the need to modify the test when the SUT modifies its interface (like the endpoint or adding/removing unrelated parameters)

\$\endgroup\$
  • \$\begingroup\$ This started me off on a nice refactor, thank you \$\endgroup\$ – Jason McFarlane Aug 13 '18 at 3:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.