What's Difference Between Unit Test and Integration Test

I have noticed there are many engineers cannot distinguish between the unit test and integration test. Even though these two tests are as different as their names, we still cannot use them in the right place. This article helps you understand them and use them correctly.

In order to explain them better, I recommend this article, which introduce the dependency injection to the unit tests to achieve more testing coverage rate. I will leverage the example in it and dive into the integration test further. Before talking about the integration test, let's take a quick look at the relationship between the unit test and the dependency injection.

Unit Test and Dependency Injection

Here comes a function, aka an unit, saveData.

function saveData(data, {q = query, con = connect} = {}) {
    /** 
    Call 'q' to execute the db query
    Call 'con' to connect to the database
    */
    con()
    const strQuery = "insert into mydatabase.mytable (data) value ('" + data +"')";
    if(q(strQuery)) {
        return true;
    } else {
        return {
            error: true,
            msg: "There was a problem saving your data"
        }
    }
}

As we have seen, we need to verify both the success case and failure case to achieve complete test coverage within the unit tests.

Therefore, we can leverage the dependency injection to prune the external dependency from a database.

describe("Unit Test", () => {
    it ("should return true if the data is saved into the database", () => {
        const result = await saveData('hi there!', {q: () => true, con: () => true})
        result.should.be.true;
    })

    it ("should return an error object if the data is not saved into the database", () => {
        const result = await saveData('hi there!', {q: () => false, con: () => true})
        result.should.equal({
            error: true,
            msg: "There was a problem saving your data"
        })
    })
}

As the above examples, we fake the database objects and make sure our business logic is correct. The keyword is "business logic". We verify the whole business logic in unit tests no matter what database is. By using the dependency injection, we can easily verify the business logic and reach a high coverage rate.

Integration Test

Alright, we have already ensure the unit works without the database. The things are not likely to go so smoothly after the database is involving. Thus, we have to make some integration tests to verify the database works as our expectations.

We have already verified the units, therefore, we can only verify the database part, i.e "insert into mydatabase.mytable (data) value ('" + data +"')" as follows.

describe("Integration Test", () => {
    it ("should save data in database", () => {
        const strQuery = "insert into mydatabase.mytable (data) value ('hello world')"
        const result = await query(strQuery)
        result.should.be.equal(1);
    })
}

This example is not structured well, because we can apply the layered architecture to build an abstraction upon SQL query, called DAL (data access layer). Hence, we can have a cleaner interface to test the database instead of using raw SQL in a test case. Moreover, in Domain-Driven Develop, there is a similar pattern, Repository, and it provides an encapsulation for the database access. Those methods are able to provide convenience for writing integration tests.

Of course you can replace the dependency injection with other techniques like mocking. However, in my opinion, mocking will introduce much more implement efforts on writing the integration tests. By using the dependency injection, we will have an independent module/object/interface for the integration.

Conclusion

Why should we distinguish between the unit test and integration test? The reason is doing integration tests will take a lot of time, most of the time from the database access. Suppose an integration test case take 100 ms, which is very fast for the database access, then it is hard for us to write thousands of test cases. In order to fully test a system, we always try to cover every decision in every function from every file, thus, controlling total time consuming is essential.

That is why Testing Pyramid shows the bottom is the unit test, and the integration test is up on it.

Let me summarize what is the main difference between unit tests and integration tests.

Unit tests are made for testing business logic; on the other hand, integration tests are for verifying the external dependencies.

With messing up the scenes, it will end up spending more effort and getting less results.

31