Question

I have a repository function on my repository layer. I use sequelize as data access. I will write tests for my function.

Here is the logic I want in English:

  • My function should save a user to database by using the email and password
  • My function should return an object representation of my created user.

For the logic I write the code below.

const UserModel = require('../models/user');

exports.create = async (email, password) => {
    const createdUser = await UserModel.create({
        email,
        password
    });

    return createdUser.toJSON();
};

So, I am planning to write integration tests. I will write some tests as below.

const userRepository = require('./repositories/user');
const userModel = require('./model/user');
const chai = require('chai');

it('should create a user by using the correct email and password', async () => {
    const email = 'testemail@test.com';
    const password = 'test@Pass123';

    const createdUser = await userRepository.create(email, password);

    const userInDb = userModel.findOne(
        {
            id = createdUser.id
        }
    );

    chai.expect(userInDb.email).to.be.equal(email);
    chai.expect(userInDb.password).to.be.equal(password);
});

it('should return object representation of created user', async () => {
    const email = 'testemail@test.com';
    const password = 'test@Pass123';

    const createdUser = await userRepository.create(email, password);

    chai.expect(typeof userInDb).to.be.equal('object');
});

So I have several questions.

  1. Isn't the test enough? Should I write unit test for it? Or what are the benefits of having unit test for my method with or without my integration test.

  2. Should I write integration test and unit test together?

  3. How can I write unit test for my method?

    Also something seems incorrect to me.

  4. My tests are depend on each other. Like, if my should return object test fails my other test could fail. Because i depent my return result will be an object in first test.

  5. My tests depend on the package I use. My repository method uses sequelize for the logic. However if I change sequelize ORM with another ORM. I have to change my tests as well.

Was it helpful?

Solution

Isn't the test enough? Should I write unit test for it?

The question seems to be valid, intuitively speaking. The integration test already confirms that the unit test is working, so why bother?

The problem with that line of thinking is that it's the same principle (though on a smaller scale) as thinking "the application runs fine as is, so why should I test parts of it?".

When you write tests, you're not interested in which parts are working, you're interested in which parts aren't working.

Integration tests are inherently tests run on more than one component/layer. A failing integration test is unable to tell you which of these parts broke, it only tells you that they aren't all working. It somewhat narrows down the problem, but not as much as a unit test would.

So yes, unit testing is relevant even if you have integration testing. Unit tests are able to precisely flag which component failed. Integration tests can't give you that level of precision, as their test result is inherently bound to the outcome of multiple components.

Are you required to unit test? Well, that's up to your discretion (or that of your project lead). It has benefits (cutting down on debugging issues), it has drawbacks (cost and effort). Whether that's a net positive or not requires a cost/benefit analysis.

Should I write integration test and unit test together?

If you mean "together" as writing it at the same time, or in the same test suite, no. These can be fully separated, and I generally advise you to separate them into their own projects. Both as a way to keep them mentally separated, and to have the ability to run one suite without the other.

However, there is no technical issue with keeping them in the same suite if you so desire.

How can I write unit test for my method?

I'm no JavaScript dev so I can't give you the exact code. But the general approach to unit testing is by using mocked dependencies.

In your case, this means that when testing exports.create, you swap out the UserModel for a mocked UserModel which doesn't actually talk to the database but instead simply returns a predefined user object that you test against.

A small C# example which (pseudo)emulates your situation and hopefully explains the general outset:

// Mock a dependency
var mockedUserModel = NSubstitute.For<UserModel>();

// When create() is called with any valid string values
// then we tell it to return John Doe as its return value.
mockedUserModel
    .create(Arg.Any<string>(), Arg.Any<string>())
    .Returns(new User() { Firstname = "John", LastName = "Doe" });

// Inject the mock into the real test subject
var exports = new Exports(mockedUserModel);

// Call the real method, which will unknowingly rely on a fake (mocked) dependency
var result = exports.create("jdoe@foo.com", "barbaz");

// Confirm that the result matches your expectations
result.Should().Be("{ 'FirstName': 'John', 'LastName': 'Doe' }");

The outset of the unit test is that given a specific behavior from the dependency (which we guarantee by mocking the dependency), that the test subject behaves accordingly (in this case, serializing it to a JSON string).

This doesn't require actual database interaction and thus it's a unit test, not an integration test.

My tests are depend on each other. Like, if my should return object test fails my other test could fail.

That's absolutely fine. Some bugs tend to impact more than one behavior of your application, which generally manifests as multiple different tests failing.

Sometimes, unit tests follow up after each other, e.g. a unit test which confirms the correct use of input validation, and a unit test which confirms that the object is stored. Logically, the second test can only pass if the input validation itself passes too.

That's not a red flag, even though it intuitively feels like one.

Imagine if you had a create() and and update() method, which both serialize their return value to JSON. Good practice dictates that this JSON serialization is reused. If something goes wrong in the JSON serialization, both of these tests will fail. That's just a logical consequence of your good practice implementation.

My tests depend on the package I use. My repository method uses sequelize for the logic. However if I change sequelize ORM with another ORM. I have to change my tests as well.

Unit tests won't have this package dependency.

Integration tests will, and it's a logical consequences than an integration test touches on the entire tech stack that is being used. If that stack changes, the test must change accordingly. This is no different than if your contracts/signatures changes - such changes will also have to occur in the tests that rely on the changed code.

Licensed under: CC-BY-SA with attribution
scroll top