4

I have written this function which checks if a user already exists in the application:

async function ValidateUserExists(username, email){
    if(!username || !email) throw new Error('Invalid number of args passed. Please pass username and email');
    let taken_valid_username = null;
    let taken_valid_email = null;
    if(username){
        taken_valid_username = await UsernameExists(username);
    }
    if(email){
        taken_valid_email = await EmailExists(email);
    }

    if(taken_valid_username) return taken_valid_username;
    if(taken_valid_email) return taken_valid_email;

    return null;
}

I have integration tests that prove UsernameExists() and EmailExists() work correctly.

It's main purpose is to see if a user already exists in the database. I can't just mock something here because both UsernameExists() and EmailExists() are designed to reach into the database and see if a user actually does exist.

I have a lot of functions like this as I'm building an app whose main purpose is to manipulate a database, meaning I have A LOT of integration testing happening.

So far the test I've written looks like this:

it('should see if a user already exists on an existent email', async ()=>{
    const test = await CreateDummyUser();
    const user = await ValidateUserExists(test.username, test.email);
    //expect user properties here
    await DestroyDummyUser(test);
})

Is there any approach to mocking/stubbing anyone has taken to attack composite functions like this? Is it sufficient enough that the functions is composed of pass?

  • 2
    You need to mock UsernameExists() and EmailExists() methods. I usually have a mock for the repository interface, and inject the repository. – BobDalgleish May 24 at 19:56
  • I would recommend sinon.js. it is a mocking library for JavaScript. sinonjs.org – Caleb May 25 at 14:41
3

In many cases, the answer is to change your design so that you can pass the capabilities you need as arguments to a new function, and then test the new function with "test doubles" replacing the things that are hard.

async function ValidateUserExists(username, email){
    return ValidateUserExistsV2(username, email, UsernameExists, ValidateUserExists);
}

async function ValidateUserExistsV2(username, email, usernameExists, validateUserExists) {
    if(!username || !email) throw new Error('Invalid number of args passed. Please pass username and email');

    let taken_valid_username = null;
    let taken_valid_email = null;

    if(username){
        taken_valid_username = await usernameExists(username);
    }
    if(email){
        taken_valid_email = await emailExists(email);
    }

    if(taken_valid_username) return taken_valid_username;
    if(taken_valid_email) return taken_valid_email;

    return null;
}

ValidateUserExistsV2 is easy to test, because in your test(s) you can define trivial implementations for usernameExists and validateUserExists; these implementations provide behaviors that mimic calls to a database.

Testing of the old function drops out of your unit test suite; most of the risk is taken care of by the testing of the new function. ValidateUserExists might count as "simple enough that we can test it with code review, but even if that isn't an option, it's certainly simple enough that we can mitigate the risks with fewer tests.

  • Dependency injection! – David Etler May 27 at 4:42
1

You need to only mock the database connection underneath your functions.

If you are using dependency injection (or even a service locator) you should be able to swap the database connection out for a mocked one quite easily.

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.