Creating an SVMock
This goes through the working example in
examples/class/bedrock. Best to refer to files in that directly as you read through this. This assumes that you have an understanding of SVUnit. If you don't, you probably want to start at the SVUnit project page first, then come back to this.
In this example, we're unit testing a class called
bedrock. There's one function in
bedrock that needs to be unit tested called
yabba_dabba_do. Specifically, we're interested in how
bedrock::yabba_dabba_do() relies on functionality from an instance of
flintstones. Instead of using the real version of
flintstones, we'll use SVMock to create a
flinstones_mock so we can directly isolate and test the interactions within
Defining a Class
flintstones includes 3 functions used by
class flintstones; function new(); endfunction virtual function void dino(); // all the code for dino endfunction virtual function int pebbles(int fred, string wilma [int]); return 12; endfunction virtual function void bam_bam(int barney); // nothing to do endfunction endclass
Mocking a Class
To create a mock of
flintstones, we'll use macros defined in svmock_defines.svh as such...
`SVMOCK(mock_flintstones, flintstones) `SVMOCK_VFUNC0(dino) `SVMOCK_FUNC2(pebbles, int, /*input*/, int, fred, /*scalar*/, /*no-default*/, /*input*/, string, wilma, [int], /*no-default*/) `SVMOCK_VFUNC1(bam_bam, /*input*/, int, barney, /*scalar*/, /*no-default*/) `SVMOCK_END
SVMOCK(mock_class, parent_class)/SVMOCK_END macros roughly translate to class/endclass.
Each function in
flintstones is mocked with an
SVMOCK_* function macro. Void functions use the
SVMOCK_VFUNC<N> macros where 'N' is the number of input arguments. The function
flintstones::dino, for example, has no input arguments so it is mocked with
flintstones::bam_bam has 1 argument so it requires the
SVMOCK_VFUNC1 macro. The first argument to the void function macros is the name of the function. Subsequent arguments are related to each input argument.
For functions that are non-void, the
SVMOCK_FUNC\<N\> macros are used. The first argument is still the name of the function. The second argument is the return type of the function. Subsequent arguments are related to each input argument.
Tasks are mocked using the `SVMOCK_TASK<N> macors. The task macro syntax is the same as the void function macros.
`SVMOCK_VFUNC<N>(NAME,<ARG0 INPUT>,<ARG1 INPUT>,...<ARGN INPUT>) `SVMOCK_TASK<N>(NAME,<ARG0 INPUT>,<ARG1 INPUT>,...<ARGN INPUT>) `SVMOCK_FUNC<N>(NAME,RETURN_TYPE,<ARG0 INPUT>,<ARG1 INPUT>,...<ARGN INPUT>)
The has inputs to cover all possible ways to declare a function/task argument. The syntax is:
| Direction | Type | Name | Aggregate type | Default | |--------------------------|------|------|-----------------------|-----------------| | [input|ref|output|inout] | type | name | [aggregate data type] | [default value] |
All inputs are required for each function/task argument (to maintain the order of the macro inputs). However, direction, aggregate type and default can take an empty string input in cases where they aren't necessary. To make it easier to maintain argument order, it's recommended to use comments for unnecessary inputs. For example, the arguments to pebbles are broken into macro arguments as...
Connecting The Mock
bedrock defined as...
class bedrock; flintstones f = new(); function void yabba_dabba_do(string wilma [int]); f.dino(); f.bam_bam(f.pebbles(wilma.num(), wilma)); endfunction endclass
...to take effect, an instance of
flintstones_mock must be created and used in place of the original
bedrock::f. This can be done in the bedrock_unit_test.sv where it's declared as our unit under test
`include "flintstones_mock.sv" ... bedrock uut; mock_flintstones mock_f = new(); ... function void build(); svunit_ut = new(name); uut = new(); uut.f = mock_f; endfunction
flinstones_mock has to be included for compilation, either as an include or as part of a package. In this example we're using an include (see above).
It's also required that the reseting and checking of the mock be invoked inside the unit test template. That's best done in the
teardown() methods so that it's automatically done for every test.
task setup(); svunit_ut.setup(); /* Place Setup Code Here */ mock_f.clear(); endtask ... task teardown(); svunit_ut.teardown(); /* Place Teardown Code Here */ `FAIL_UNLESS(mock_f.verify()); endtask
Using The Mock
Currently, users can set expectations for how many times a function has been called as well as the arguments it is invoked with. You can also override function return values.
Expect Functions Are Invoked
In our first unit test, we simply want to verify that the function
dino() is invoked in
bedrock::yabba_dabba_do. We can do that with an EXPECT_CALL.
`SVTEST(dino_called_once) `EXPECT_CALL(mock_f, dino).exactly(1); // dino should be called exactly once uut.yabba_dabba_do(betty); `SVTEST_END
Since we expect
dino() to be called exactly once, we use
exactly(1). Other options for an EXPECT_CALL that verifies whether or not a function has been called are
||function is called exactly 'N' times|
||function is called at least 'N' times|
||function is called at most 'N' times|
||function is called at least 'N' and at most 'M' times|
Expect Function Arguments
We can also use a
flintstones_mock EXPECT_CALL to check functions are called with specific arguments. For example, we can expect that
f.pebbles is invoked from
yabba_dabba_do with arguments derived from its input argument. Setting expectations for arguments is done with a
`SVTEST(pebbles_called_with_betty) `EXPECT_CALL(mock_f, pebbles).with_args(betty.num(), betty); // expect yabba_dabba_do invokes pebbles(betty.num(), betty) uut.yabba_dabba_do(betty); `SVTEST_END
with_args are identical to the inputs of the function being checked. In this case, we're checking inputs to
pebbles which are
(int , string [int]) so
with_args has the same
(int , string [int]) inputs. Currently, all inputs must be specified.
Overriding Return Values
The mock can be used to override return values of functions. In a third test, we want to confirm
bam_bam() is invoked with the return value of
pebbles() without concerning ourselves with the actual functionality of pebbles or it's realistic return value. We can use ON_CALL to to override the return value of
pebbles(), then use EXPECT_CALL to verify that the value we specify is indeed used as input to
`SVTEST(bam_bam_called_with_pebbles) `ON_CALL(mock_f, pebbles).returns(99); // Override pebbles return value `EXPECT_CALL(mock_f, bam_bam).with_args(99); // expect bam_bam is called with the override value uut.yabba_dabba_do(betty); `SVTEST_END
Overriding A Method
If simply overriding a return value isn't enough or you need to replace functionality of a method, you can override that mocked method with something entirely new. For example, the functionality of
dino() could replaced by calling
mr_slate() instead. To do this, we need to define
mr_slate() then map it to
dino() with an SVMOCK_MAP_FUNC in
`SVMOCK_MAP_VFUNC0(dino, mr_slate) bit mr_slate_instead; function void mr_slate(); mr_slate_instead = 1; endfunction
mr_slate() is mapped to
dino(), we can then override
dino() in our 4th unit test using a
will_by_default ON_CALL. Notice that
will_by_default takes a string input which is the name of the newly mapped function.
`SVTEST(mr_slate_instead_of_dino) `ON_CALL(mock_f, dino).will_by_default("mr_slate"); uut.yabba_dabba_do(betty); `FAIL_UNLESS(mock_f.mr_slate_instead) `SVTEST_END
Important to note that the signature of
mr_slate() is exactly the same as the signature for
dino() (i.e. neither function has input arguments). For another example, if we were to map
pebbles() to a new function called
rock_quarry() would require similar
wilma input arguments.
`SVMOCK_FUNC2(pebbles, int, int, fred, , string, wilma, [int]) ... `SVMOCK_MAP_FUNC2(pebbles, rock_quarry) function int rock_quarry(int fred, string wilma [int]); // a replacement for pebbles endfunction
There's a different macro available for each possible method type.
`SVMOCK_MAP_VFUNC<N>(OLD_FUNCTION, NEW_FUNCTION) `SVMOCK_MAP_TASK<N>(OLD_FUNCTION, NEW_FUNCTION) `SVMOCK_MAP_FUNC<N>(OLD_FUNCTION, NEW_FUNCTION)
Initialization and Checking
To ensure the mock state is reset for each test, the
clear() function for the mock should be called in the
setup() method of the unit test template. Similarly, for the checking to be invoked it's necessary to call the
verify() function in the mock.
verify() will return 1 (true) when expectations are met, 0 (false) otherwise). It's recommended you can call
verify() in the teardown method so it's automatically invoked at the end of each test.
We're at the tip of the ice berg here so there's more to come.