При написании юнит-тестов частой проблемой является подмена функций, вызываемых внутри тестируемой функции (mocking/stubbing).
Очень интересует как это решается в таких языках программирования как Java и C/C++, где есть строгая типизация.
К примеру в python, ruby, js можно инжектить фейковые модули, классы, объекты, функции как аргументы функции или класса (dependency injection). Насколько я знаю, с Java понятие dependency injection также тесно связано. Интересно, как в java и плюсах подменяются импортированные классы и модули на фейковые при юнит-тестировании.
Dependency Injection как раз эту проблему и решает. Все зависимости приходят извне, соответственно в юнит тестах можно просто подсунуть моки в сеттеры или конструкторы.
С точки зрения строгой типизации, то тут тоже ничего военного. Мок должен быть того же типа, что и ожидаемый объект (наследоваться от того же класса и имплементить те же интерфейсы). С технической точки зрения реализуется либо dynamic proxy, либо генерацией класса-наследеника оригинального ожидаемого типа.
В джавке вообще есть библиотеки типа Mockito, которые очень упрощают написание моков/стаббов.
@olyv Проблема может быть в том, что функция (класс, модуль) может быть недоступна снаружи (в тесте) для подмены из-за ограничений области видимости. Н-р в случае c javascript:
// file.js
var Server = require("some-module").Server;
module.exports.func = () => {
var server = new Server({ host: "localhost", port: 8888 });
server.start();
};
// test.js
var func = require("./file").func;
describe("my tests", () => {
it("should start server", () => {
func(); // here I can't observe server, because it's internal
});
});
Но если поменять слегка код, то можно сделать инжекцию фейкового класса.
// file.js
var Server = require("some-module").Server;
module.exports.func = opts => {
opts = opts || {};
var S = opts.Server || Server;
var server = new S({ host: "localhost", port: 8888 });
server.start();
};
// test.js
var expect = require("chai").expect;
var sinon = require("sinon");
var func = require("./file").func;
describe("my tests", () => {
var sOpts, start, Server;
before(() => {
Server = function (opts) {
sOpts = opts;
this.start = start = sinon.spy();
};
})
it("should start server", () => {
func({ Server: Server });
expect(sOpts.host).to.be.equal("localhost");
expect(sOpts.port).to.be.equal(8888);
expect(start.calledOnce).to.be.true
});
});