I'm new to swig and python unittesting.
Here's what I'm trying to do. I have a c++ function that requires user input. The C++ code is wrapped into python code using SWIG. I'm trying to use pythons unittest module to mock the input. Ive tried mocking builtins.input and writing my own basic function in c++ that just returns a string and mocking that.
Mocking builtins.input still hangs when I get to the std::cin in the c++ code. and mocking the function that returns a string, doesnt return the mocked return_value.
My guess is for some reason I cant mock the return value of the function because its really c++ code and not true python.
Here's some example code I'm working with:
c++ I can include the header file is needed, but its really simple.
#include "MockTest.h"
#include <iostream>
#include <string>
MockTest::MockTest() {}
std::string MockTest::getInput()
{
std::string name;
std::cout << "Enter you name" << std::endl;
name = this->mockInput("hi"); //std::cin >> name;
std::string toReturn = "Hello " + name + " person";
return toReturn;
}
std::string MockTest::mockInput(std::string input)
{
return input;
}
swig interface file:
%module MockTest
%include "std_string.i"
%include "std_vector.i"
%include "std_iostream.i"
%{
#include "MockTest.h"
%}
%include <MockTest.h>
python test script
from unittest.mock import patch
from unittest import TestCase
import unittest
import MockTest
class Test(TestCase):
@patch('builtins.input', return_value="Will")
def test_Mocktest(self, input):
self.assertEqual(MockTest.MockTest().getInput(), 'Hello Will person')
@patch('MockTest.MockTest.mockInput', return_value="Will")
def test_Mocktest2(self, mockInput):
self.assertEqual(MockTest.MockTest().getInput(), 'Hello Will person')
if __name__ == '__main__':
unittest.main()
After spending some time out of which ~ one hour on a stupid mistake:
public:
before class members in MockTest.h ... X:((((and some more time investigating the real problem, I finally reached a conclusion: the problem is not because of the C++ language (at least not directly), but it lies in the intermediary layer(s) created by Swig.
The MockTest
class (whose mockInput
method return value we are trying to patch
) is defined in module MockTest
(!!! that is MockTest.py !!!) which is automatically generated by Swig (the command in my case was swig -o MockTest_wrap.cpp -python -c++ MockTest.i
). Here's its definition:
class MockTest(_object):
__swig_setmethods__ = {}
__setattr__ = lambda self, name, value: _swig_setattr(self, MockTest, name, value)
__swig_getmethods__ = {}
__getattr__ = lambda self, name: _swig_getattr(self, MockTest, name)
__repr__ = _swig_repr
def __init__(self):
this = _MockTest.new_MockTest()
try:
self.this.append(this)
except __builtin__.Exception:
self.this = this
__swig_destroy__ = _MockTest.delete_MockTest
__del__ = lambda self: None
def getInput(self):
return _MockTest.MockTest_getInput(self)
def mockInput(self, input):
return _MockTest.MockTest_mockInput(self, input)
As you probably guessed, this mockInput
is being patch
ed and not the one from C++ (whose name is MockTest_mockInput
and it's exported by the native module: _MockTest
- the native name is _wrap_MockTest_mockInput
defined in MocTest_wrap.cpp). Of course, since getInput
calls MockTest_getInput
, patch
ing mockInput
(the Python wrapper) has no effect (just like if you would modify its body to let's say: return "123"
).
Here's some sample code that I prepared to better illustrate the behavior (as I mentioned in my comment, I'm using Python 3.5.4). Please ignore the last 3 lines of code and output til you read the next paragraph:
from unittest.mock import patch
import MockTest
if __name__ == "__main__":
return_value = "value overridden by `patch`"
mock_input_arg = "argument passed to `MockTest.mockInput`"
mock_test = MockTest.MockTest()
with patch("MockTest.MockTest.mockInput", return_value=return_value):
print("`mock_test.getInput()` returned: \"{}\"".format(mock_test.getInput()))
print("`mock_test.mockInput(\"{}\")` returned: \"{}\"".format(mock_input_arg, mock_test.mockInput(mock_input_arg)))
print("\nDon't mind the following output for now...\n") # SAME THING about the following code
with patch("MockTest._MockTest.MockTest_mockInput", return_value=return_value):
print("`mock_test.getInput()` returned: \"{}\"".format(mock_test.getInput()))
print("`mock_test.mockInput(\"{}\")` returned: \"{}\"".format(mock_input_arg, mock_test.mockInput(mock_input_arg)))
And the output:
c:\Work\Dev\StackOverflow\q45934545>"c:\Install\x64\Python\Python\3.5\python.exe" dummy.py Enter you name `mock_test.getInput()` returned: "Hello hi person" `mock_test.mockInput("argument passed to `MockTest.mockInput`")` returned: "value overridden by `patch`" Don't mind the following output for now... Enter you name: `mock_test.getInput()` returned: "Hello hi person" `mock_test.mockInput("argument passed to `MockTest.mockInput`")` returned: "value overridden by `patch`"
Then I went further trying to patch
MockTest._MockTest.MockTest_mockInput
, but I got the same output, because I didn't patch
MockTest::mockInput
(from MockTest.cpp) but _wrap_MockTest_mockInput
(from MocTest_wrap.cpp).
Below is a table with all the layers between C++ code and Python for mockInput
(for getInput
it's exactly the same):
As getInput
calls mockInput
(layer 0), there is where the patch
should happen, but unfortunately, that's not available to Python.
The 1st solution that comes into my mind, is patch
ing getInput
(layer 1) directly (if it's used in many places).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With