How can I make a mock method return different answers?

2 views (last 30 days)
I am trying to use the mocking framework to test a class method that computes a running variance of a stream of image frames. The input to the method would normally come from another data class. What I would like to do is to have a mock that returns a normally distributed random frame each time it gets called, and then have a test that checks to see if, after some large number of frames, the returned variance is 1 (within a tolerance).
If I write my method like this:
function mock = makeMock2(obj)
[mock, b] = createMock(obj, 'AddedMethods', {'r'});
import matlab.mock.actions.AssignOutputs;
rng(1)
x = @() randn(obj.rows, obj.cols);
when(withAnyInputs(b.r), repeat(32, AssignOutputs(x())));
end
Then when the output is assigned, randn is evaluated and the output is static, so all my inputs are the same. The second thing I tried was to add multiple frames all at once as a list:
function mock = makeMock3(obj)
[mock, b] = createMock(obj, 'AddedMethods', {'r'});
import matlab.mock.actions.AssignOutputs;
rng(1)
x = num2cell(randn(obj.rows, obj.cols, 32), [1 2]);
when(withAnyInputs(b.r), AssignOutputs(x{:}));
end
This still gives only the first frame because AssignOutputs deals its values to the output variables, and so I'm always only requesting the first one.
The last ugly thing I tried was:
when(withAnyInputs(b.r), ...
then(AssignOutputs(randn(obj.rows, obj.cols)), ...
then(AssignOutputs(randn(obj.rows, obj.cols)), ...
then(AssignOutputs(randn(obj.rows, obj.cols)), ...
then(AssignOutputs(randn(obj.rows, obj.cols)), ...
then(AssignOutputs(randn(obj.rows, obj.cols)), ...
then(AssignOutputs(randn(obj.rows, obj.cols)), ...
then(AssignOutputs(randn(obj.rows, obj.cols)), ...
then(AssignOutputs(randn(obj.rows, obj.cols)), ...
then(AssignOutputs(randn(obj.rows, obj.cols)), ...
then(AssignOutputs(randn(obj.rows, obj.cols)), ...
then(AssignOutputs(randn(obj.rows, obj.cols)), ...
then(AssignOutputs(randn(obj.rows, obj.cols)), ...
then(AssignOutputs(randn(obj.rows, obj.cols)), ...
then(AssignOutputs(randn(obj.rows, obj.cols)), ...
then(AssignOutputs(randn(obj.rows, obj.cols)), ...
then(AssignOutputs(randn(obj.rows, obj.cols)), ...
then(AssignOutputs(randn(obj.rows, obj.cols)), ...
then(AssignOutputs(randn(obj.rows, obj.cols)), ...
then(AssignOutputs(randn(obj.rows, obj.cols)), ...
then(AssignOutputs(randn(obj.rows, obj.cols)), ...
then(AssignOutputs(randn(obj.rows, obj.cols)), ...
then(AssignOutputs(randn(obj.rows, obj.cols)), ...
then(AssignOutputs(randn(obj.rows, obj.cols)), ...
then(AssignOutputs(randn(obj.rows, obj.cols)), ...
then(AssignOutputs(randn(obj.rows, obj.cols)), ...
then(AssignOutputs(randn(obj.rows, obj.cols)), ...
then(AssignOutputs(randn(obj.rows, obj.cols)), ...
then(AssignOutputs(randn(obj.rows, obj.cols)), ...
then(AssignOutputs(randn(obj.rows, obj.cols)), ...
then(AssignOutputs(randn(obj.rows, obj.cols)), ...
then(AssignOutputs(randn(obj.rows, obj.cols)), ...
AssignOutputs(randn(obj.rows, obj.cols))) ...
)))))))))))))))))))))))))))))));
This gives me an error that says that I am nested too deep.
I will note that the methods I'm trying to test want the whole mock object as an input, not just the frame, so just passing in a random frame isn't possible. It's part of a much, much larger code base and so simply rewriting the method under test to take the frame directly is not currently possible without significantly refactoring many others as well, so I'm stuck doing the test this way for now.
Is there an elegant way to do what I want to do?
  1 Comment
Markus Leuthold
Markus Leuthold on 8 Aug 2023
"Invoke" was implemented in 2018b, could be your question triggered this enhancement :)
testCase = matlab.mock.TestCase.forInteractiveUse;
[mock,behavior] = testCase.createMock("AddedMethods","roll");
import matlab.mock.actions.Invoke
when(withExactInputs(behavior.roll),Invoke(@(~)randi(6)))
val1 = mock.roll
val2 = mock.roll

Sign in to comment.

Accepted Answer

David Hruska
David Hruska on 18 Sep 2017
Unfortunately, there isn't a clean way to do this. I'll record this question in an enhancement request, though, for consideration for a future release.
Building on the last workaround you tried: the action can be built up in a loop. This avoids most of the duplication and resulting deep nesting. Here's an example:
import matlab.mock.actions.AssignOutputs;
[mock, b] = createMock(obj, 'AddedMethods', {'r'});
rng(1)
n = 32;
action = AssignOutputs(randn(obj.rows, obj.cols));
for i = 1:n-1
action = action.then(AssignOutputs(randn(obj.rows, obj.cols)));
end
when(withAnyInputs(b.r), action);
Then, when you use the mock (I specified rows = cols = 1 for brevity):
for i = 1:n
disp(mock.r);
end
It returns the sequence of random numbers as desired:
-0.6490
1.1812
-0.7585
-1.1096
-0.8456
-0.5727
-0.5587
0.1784
-0.1969
0.5864
-0.8519
0.8003
-1.5094
0.8759
-0.2428
0.1668
-1.9654
-1.2701
1.1752
2.0292
-0.2752
0.6037
1.7813
1.7737
-1.8651
-1.0511
-0.4174
1.4022
-1.3677
-0.2925
1.2708
0.0660
You just have to select "n" large enough when setting up the mock as any further calls to the method will return this last value.
  1 Comment
Ian Craig
Ian Craig on 18 Sep 2017
Thanks, that's perfect! My test already has a property that defines the number of frames that the mock has to deliver, so I just use that instead of n in your answer.

Sign in to comment.

More Answers (1)

Markus Leuthold
Markus Leuthold on 8 Aug 2023
testCase = matlab.mock.TestCase.forInteractiveUse;
[mock,behavior] = testCase.createMock("AddedMethods","roll");
import matlab.mock.actions.Invoke
when(withExactInputs(behavior.roll),Invoke(@(~)randi(6)))
val1 = mock.roll
val2 = mock.roll

Products

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!