Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mock standard library functions in D

I have a function that calls isFile (from std.file) on a filename and then proceeds appending .1, .2, .3 etc, checking whether each one of those is present.

I want to unit test the function, but to do so I need to mock isFile.

I looked around a bit and I found ways to mock classes but not single functions.

like image 211
garph0 Avatar asked Feb 12 '23 19:02

garph0


2 Answers

Since my answer is slightly different from Adam's, I will add it, and he can add his.

You can use "Scoped imports" for that purpose. See the respective section in the documentation http://dlang.org/module.html

Here's also a working example, how you can mock an isFile function inside a unittest block (assuming it is defined in module "mocks")

import std.file; 
import std.stdio;

int main(string[] args) 
{ 
    writeln(isFile("qq.d")); 
    return 0; 
} 

unittest 
{ 
    import mocks;
    writeln(isFile("qq.d")); 
}
like image 72
Sergei Nosov Avatar answered Feb 20 '23 03:02

Sergei Nosov


My simple solution is to mock the functions in a separate module, then use version(unittest) to choose which one you want:

version(unittest)
   import mocks.file;
else
   import std.file

void main() { isFile("foo"); } // std.file normally, mocks.file in test mode

The local import Sergei Nosov works in some cases, but I think the top level one is better because typically you'd want to test your own function:

string test_me() { isFile("qq.d"); return "do something"; }
unittest {
    assert(test_me() == "do something");
}

In that case, the scoped import wouldn't work because isFile is used too far away from the test. The version(unittest) on the import at the usage point, however, could redefine the function as needed.

Perhaps the best would be a combination:

string test_me() {
    version(unittest) bool isFile(string) { return true; }
    else import std.file : isFile;
    isFile("qq.d"); return "do something";
 }

That is, defining the fake function locally... but I don't like that either, now that I think of it, since the function doesn't necessarily know how it will be tested. Maybe the mocks module that is imported actually makes function pointers or something that can be reassigned in the unittest block.... hmm, it may need to be a full blown library, not just a collection of functions.

But I think between our two answers, there's a potential solution.


Third thing I want to mention, though it is kinda insane, is that it is possible to globally replace a function in another module by using some linker tricks:

import std.file;
import std.stdio;

// our replacement for isFile...
pragma(mangle, std.file.isFile.mangleof)
static bool isFile(string) { return true; }

int main(string[] args)
{
    writeln(isFile("qq.d")); // always does true
    return 0;
}

The reason that works is pragma(mangle) changes the name the linker sees. If the linker sees two functions with the same name, one in a library and one in the user code, it allows the user code to replace the individual library function.

Thus, our function is used instead of the lib. Important notes with this: the function signatures must match, or else it will crash when you run it, and it replaces the function for your entire program, not just one location. Could be used with version(unittest) though.

I don't recommend actually using this trick, it is prone to random crashes if you make a mistake, just wanted to throw it out there while thinking about replacing std lib functions.

Perhaps this trick plus function pointers could be used to replace a function at run time. Major problem with that though: since the linker completely replaces the library function with your function, you can't actually use the original implementation at all!

You could also replace a whole std lib module by writing your own, giving it the same name, and passing it to the compiler explicitly. I sometimes do that while doing development work on Phobos. But since this replaces the whole thing and is a compiler command line difference, it probably isn't helpful for unit tests either.

like image 31
Adam D. Ruppe Avatar answered Feb 20 '23 03:02

Adam D. Ruppe