Skip to content

Proposal: mocking support #500

Closed as not planned
Closed as not planned
@PavelVozenilek

Description

@PavelVozenilek

Definition from Wikipedia:

In a unit test, mock objects can simulate the behavior of complex, real objects and are therefore useful when a real object is impractical or impossible to incorporate into a unit test.

When one tries to do mocking in C++ he usually needs to:

  1. Create abstract base classes (interfaces) for everything that may be mocked.
  2. Place all mockable functionality into subclasses and virtual functions.
  3. Create mechamism to replace mockable parts with mocks (e.g. via dependency injection).

Something what on first sight looks rather simple turns into nightmare. Design needs changes to allow mocking, internal details have to be made public, complex frameworks are invented to support mocking.


My proposal: if a test needs to mock something just redefine that thing inside the test.

test "with mock" 
{
  // "re-define" is context dependent keyword
  re-define fn fopen(name : string) -> int { ... } // dummy replacements
  re-define fn fclose(int handle) { ... }

  // test code opening and closing files, doesn't actually touch the disk
}

Whenever compiler sees a test containing redefinitions it acts as if original functions do not exist anymore and uses redefinitions instead. For code outside this specific test nothing changes.

(I suppose that names are unique in Zig, so no redefinition will be ambiguous.)


Original functionality may still be accessed:

test "with mock" 
{
  re-define fn fopen(name : string) -> int { 
    ...
    var id = ::fopen(...); // leading :: means original definition in this context
    ...
    return id;
  }
  ...
}

Redefined functions may access test local variables as "globals", to communicate intent and results. I believe access to parent's locals is planned feature for inner functions, this is similar.

test "with mock" 
{
  var expected result : int;
  ...
  re-define fn fopen(name : string) -> int { 
    return expected result;
  }
  ...
}

Unit test may need imports to do the redefinitions:

test "with mock" 
{
  import "whatever needed"
  re-define const some_datatype = struct { ... }
  re-define fn some_function(val : some_datatype) { ... }
  ...
}

Anything could be mocked, e.g. constants, struct methods, types ...

test "with mock" 
{
  re-define const TIMEOUT_MILLIS = 0.1;
  ...
}

Implementation: I am not sure whether such feature could be easily implemented. One (not very scalable) way is to do complete project recompilation for every single test with redefinitions, doing replacement on source code level.


Situations for which suggested feature is not appropriate: very complicated mocks that would require lot of redefinitions. These situations would be better handled in traditional way, with design changes.

Metadata

Metadata

Assignees

No one assigned

    Labels

    proposalThis issue suggests modifications. If it also has the "accepted" label then it is planned.

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions