I'm learning how to write unit tests on a project I'm doing with my friends. One thing we tried was to mock the return values of function calls if the functions are declared in a different "layer". For example, if I have a function in the routes/
directory that calls another function in the services/
directory, my unit tests in the routes
directory will mock the return values of function calls where the functions are declared in the services
directory. I thought this would speed up the execution of the tests and make them easier to maintain. However, we noticed that this approach makes the tests less reliable at capturing changes caused by refactoring.
I'll try to give a demonstration of my problem with pseudocode. Here's pseudocode for two functions in two different layers where one calls another:
// routes/calendar.js
function expiredDriversLicense(httpRequest, response) {
const result = services.dateDiff(httpRequest.get.dateA, httpRequest.get.dateB);
const isExpired = result > 3600*24*365;
response.status(200).send({isExpired:isExpired});
}
// services/calendar.js
function timeDiff(dateA, dateB) {
return dateA.getUnixTime() - dateB.getUnixTime();
}
My unit tests for routes/calendar.js
mocks results from the services.dateDiff()
function, so it looks like this:
function testExpiredDriversLicense() {
expiredDriversLicense.mock.returnResult(100);
const httpRequest = {get:{dateA:"some date", dateB:"some date"}}
expiredDriversLicense(httpRequest);
assert response.send.called[0].arguments[0] === {isExpired:false};
}
The above worked fine initially.
But later, my friends refactored services/calendar.js:timeDiff()
by changing the order of incoming arguments. For example, he changed the function to this:
// services/calendar.js
function timeDiff(dateB, dateA) {
return dateA.getUnixTime() - dateB.getUnixTime();
}
Notice the dateB
and dateA
changed places. There were a lot of unit tests for services/calendar.js
and my friend updated all of them while refactoring the services layer. However, my friend did not refactor routes/calendar.js:expiredDriversLicense()
because he didn't realize it depends on services/calendar.js:timeDiff()
. And because my unit tests for routes/calendar.js:expiredDriversLicense()
mocks all calls to and from the services layer, none of router unit tests failed.
Is scenario above an example of bad implementation of unit testing? Or inappropriate use of mocks? Did I completely misunderstood the way unit tests are supposed to be implemented?