Unit testing in Python: Nesting mocked objects - python-3.x

I am new to unit testing in Python. I am trying to unit test a method that does something like this:
MyClass:
client : Client
def __init__(client):
self.client=client
def method_to_test(self):
images = self.client.get_stuff()
image = images[0]
result = []
for region in image.regions:
result.append(region.region_id)
return result
I have tried mocking and nesting of all the classes involved: Client, Image and ImageRegion. But at some point the mocking breaks down and my test fails. Is there a way to mock things so that one can do something like:
def test_method_to_test():
result = MyClass(mock_client).method_to_test()
assert dummy_region_id in result
... or perhaps better ways to test in such situations with nested custom objects?
My latest attempt looks like this:
with patch('package.module.ImageRegion') as MockRegion:
region = MockRegion()
region.region_id.return_value = dummy_region_id
with patch('package.module.Image') as MockImage:
image = MockImage()
image.regions.return_value = [region]
with patch('package.module.Client') as MockClient:
mock_client = MockClient()
mock_client.get_regions.return_value = [image]
result = MyClass(mock_client).method_to_test()
assert dummy_region_id in result

You could create mocked object using unittest.mock and use Dependency Injection to pass the mocked object to MyClass.
E.g.
myclass.py:
class MyClass:
def __init__(self, client):
self.client = client
def method_to_test(self):
images = self.client.get_stuff()
image = images[0]
result = []
for region in image.regions:
result.append(region.region_id)
return result
test_myclass.py:
import unittest
from unittest.mock import Mock
from collections import namedtuple
from myclass_63948475 import MyClass
Image = namedtuple('Image', ['regions'])
Region = namedtuple('Region', 'region_id')
class TestMyClass(unittest.TestCase):
def test_method_to_test(self):
regions = [Region('1'), Region('2'), Region('3')]
images = [Image(regions=regions)]
mock_client = Mock()
mock_client.get_stuff.return_value = images
myclass = MyClass(mock_client)
actual = myclass.method_to_test()
mock_client.get_stuff.assert_called_once()
self.assertEqual(actual, ['1', '2', '3'])
if __name__ == '__main__':
unittest.main()
unit test result with coverage report:
coverage run /Users/ldu020/workspace/github.com/mrdulin/python-codelab/src/stackoverflow/63948475/test_myclass_63948475.py && coverage report -m
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
Name Stmts Miss Cover Missing
-----------------------------------------------------------------------------------
src/stackoverflow/63948475/myclass_63948475.py 10 0 100%
src/stackoverflow/63948475/test_myclass_63948475.py 18 0 100%
-----------------------------------------------------------------------------------
TOTAL 28 0 100%

Related

pytest run the test in subprocess from within fixture

I have a fixture that wraps all of my tests, for example checking the GPU memory usage:
import pytest
import nvidia_smi
def gpu_memory_used():
nvidia_smi.nvmlInit()
device_count = nvidia_smi.nvmlDeviceGetCount()
assert device_count == 1, 'Should be 1 GPU'
handle = nvidia_smi.nvmlDeviceGetHandleByIndex(0)
info = nvidia_smi.nvmlDeviceGetMemoryInfo(handle)
used_memory = info.used
nvidia_smi.nvmlShutdown()
return used_memory
#pytest.fixture(autouse=True)
def check_gpu_memory():
memory_used_before_test = gpu_memory_used()
yield
memory_used_after_test = gpu_memory_used()
assert memory_used_after_test == memory_used_before_test
I'm wondering is it possible to run the yield in a subprocess and return the PASS/FAIL of the test to the fixture -> Important to note that I want the subprocess to be closed once done.
Thanks

Setting read_data after calling mock.mock_open()

I am testing a class that in turn uses open. I want to patch open on the test class for simplicity, but set read_data per test. So something like this:
#mock.patch('builtins.open', unittest.mock.mock_open())
class test_TheClassUnderTest:
def test_MakeSureAMethodWorks(self):
open().read_data = "custom data for the test"
I confirmed that tests work when I set read_data in the patch decorator:
#mock.patch('builtins.open', unittest.mock.mock_open(read_data="acid burn and zero cooooooool"))
class test_TheClassUnderTest(unittest.TestCase):
def test_MakeSureAMethodWorks(self):
# ...
self.assertEquals(object.a_method(), expected_value)
I've tried setting the return values of the mock returned from open():
open().read.return_value = "custom data for the test"
open().readline.return_value = "custom data for the test"
but it's not working. Is there a way to set the value of read_data after the mock_open() constructor has been called or, more generally, another way to do this?
You don't need to mock the .return_value for open().read. Use mock_open(read_data="fake data") is enough.
E.g.
main.py:
def main(file_path):
lines = []
with open(file_path) as f:
for line in f:
lines.append(line)
return lines
test_main.py:
import unittest
from unittest.mock import mock_open, patch
from main import main
class TestMain(unittest.TestCase):
def test_main(self):
m = mock_open(read_data="custom data for the test\nsecond line")
with patch('builtins.open', m) as mocked_open:
actual = main('fake/file/path')
self.assertEqual(actual, ['custom data for the test\n', 'second line'])
mocked_open.assert_called_once_with('fake/file/path')
if __name__ == '__main__':
unittest.main()
test result:
⚡ coverage run /Users/dulin/workspace/github.com/mrdulin/python-codelab/src/stackoverflow/65533628/test_main.py && coverage report -m --include='./src/**'
.
----------------------------------------------------------------------
Ran 1 test in 0.010s
OK
Name Stmts Miss Cover Missing
-----------------------------------------------------------------------
src/stackoverflow/65533628/main.py 6 0 100%
src/stackoverflow/65533628/test_main.py 12 0 100%
-----------------------------------------------------------------------
TOTAL 18 0 100%

How to test the GET method that takes parser inputs in flask/flask-restx?

I am making a flask app using Flask-restx and I take inputs from the user by request parsing as follows:
from flask_restx import Resource, reqparse
from .services.calculator import DimensionCalculator
parser = reqparse.RequestParser()
parser.add_argument("dimensions", type=float,
required=True,
action='split',
help="Dimensions of the rectangle (in meters)")
parser.add_argument("angle_inclination", type=float,
required=True,
action='append',
help="Angle of inclination of the Dachfläche (Neigung)")
#ns.route("/output")
class UserOutput(Resource):
#ns.expect(parser, validation=True)
def get(self):
args = parser.parse_args()
return DimensionCalculator.inputs(**args)
where ns is a namespace I have defined and the simplified version of DimensionCalculator.inputs is:
class DimensionCalculator:
def inputs(**user_input):
installation_place = user_input['installation_place']
num_rectangles = user_input['num_rectangles']
dimensions = user_input['dimensions']
angle_inclination = user_input['angle_inclination']
alignment = user_input['alignment']
direction = user_input['direction']
vendor = user_input['vendor']
output = {
"installation_place": installation_place,
"num_rectangles": num_rectangles,
"area_shape": area_shape,
"vendor": vendor
}
return output
I am writing tests using pytest. I have written the tests for all the classes and methods and the only one that I am unable to test is the GET method defined in the UserOutput. Is there a way to test the GET method?
Any help is appreciated.
Considering unit-testing tag, I'll present what I came up with on how you could test it in total isolation. Basically, get method makes two function calls on dependencies, so in unit sense, you have to check if these calls have indeed been made, as well as assert the arguments, right?
Project structure for purpose of the example:
+---Project
| |
| | __init__.py
| | config.py
| | dimension_calculator.py
| | parser_impl.py
| | user_output.py
| | user_output_test.py
So, everything is flat for simplicity.
Most importantly, you have to decouple your UserOutput module from dependencies. You shouldn't be hard-coding dependencies like that:
from .services.calculator import DimensionCalculator
Hypothetically, DimensionCalculator could contain complex business logic which shouldn't be in scope of the test. So, here's how the UserOutput module could look like:
from flask_restx import Resource, Api
from flask import Flask
from .config import Config
app = Flask(__name__)
api = Api(app)
ns = api.namespace('todos', description='TODO operations')
#ns.route("/output")
class UserOutput(Resource):
#ns.expect(Config.get_parser_impl(), validation=True)
def get(self):
args = Config.get_parser_impl().parse_args()
return Config.get_dimension_calculator_impl().inputs(**args)
if __name__ == '__main__':
app.run(debug=True)
As you can see, "external" dependencies can now be easily stubbed (this is part of a common pattern called dependency injection). Config module looks as follows:
from .parser_impl import parser
from .dimension_calculator import DimensionCalculator
class Config(object):
parser_impl = parser
calculator = DimensionCalculator
#staticmethod
def configure_dimension_calculator_impl(impl):
Config.calculator = impl
#staticmethod
def configure_parser_impl(impl):
Config.parser_impl = impl
#staticmethod
def get_dimension_calculator_impl():
return Config.calculator
#staticmethod
def get_parser_impl():
return Config.parser_impl
Last, but not least, is the place where we'll be stubbing the dependencies and injecting them:
from .user_output import UserOutput
from flask import Flask
from .config import Config
class ParserStub(object):
parse_args_call_count = 0
#staticmethod
def parse_args():
ParserStub.parse_args_call_count = ParserStub.parse_args_call_count + 1
return {'parser_stub': 2}
class DimensionCalculatorStub(object):
inputs_call_count = 0
#staticmethod
def inputs(**args):
DimensionCalculatorStub.inputs_call_count = DimensionCalculatorStub.inputs_call_count + 1
return {'stub': 1}
app = Flask(__name__)
def test_user_request_get():
with app.test_request_context():
# given
Config.configure_dimension_calculator_impl(DimensionCalculatorStub)
Config.configure_parser_impl(ParserStub)
uo = UserOutput()
# when
uo.get()
# then
assert DimensionCalculatorStub.inputs_call_count == 1
assert ParserStub.parse_args_call_count == 1
# assert arguments as well!
Test passes in my case. One thing missing is validation of arguments.
For completeness, I'll also include DimensionCalculator and the parser itself, though they are exactly the same as in your example. I've only modularized them:
from flask_restx import reqparse
parser = reqparse.RequestParser()
parser.add_argument("dimensions", type=float,
required=True,
action='split',
help="Dimensions of the rectangle (in meters)")
parser.add_argument("angle_inclination", type=float,
required=True,
action='append',
help="Angle of inclination of the Dachfläche (Neigung)")
and the dimension_calculator.py:
class DimensionCalculator:
#staticmethod
def inputs(**user_input):
installation_place = user_input['installation_place']
num_rectangles = user_input['num_rectangles']
dimensions = user_input['dimensions']
angle_inclination = user_input['angle_inclination']
alignment = user_input['alignment']
direction = user_input['direction']
vendor = user_input['vendor']
output = {
"installation_place": installation_place,
"num_rectangles": num_rectangles,
"area_shape": "EMPTY",
"vendor": vendor
}
return output
Important definitely there are dedicated frameworks for such stubs/mocks preparation and configuration (for example: https://pypi.org/project/pytest-mock/). I just wanted to present the concept and easiest approach possible.

Pytest object created by object assert_called_once_with

I known how I can test if an injected object was called with a specific argument. But in my case the injected object will create an object that object will create another object and I want to test if that last object was called with the right argument.
in the example below the question would be if c.dirve was called with 100 as argument:
class car:
def drive(self, distance):
print("so fast")
class car_shop:
def buy_car(self):
return car()
class shop_shop:
def buy_shop(self):
return car_shop()
class processor:
def __init__(self, sshop):
self.sshop = sshop
def run(self):
cshop = self.sshop.buy_shop()
c = cshop.buy_car()
c.drive(100)
def main():
sshop = shop_shop()
proc = processor(sshop)
proc.run()
if __name__ == "__main__":
main()
is there a way to test that?
Since this was requested here my approach for testing these objects:
import pytest
from unittest.mock import Mock
from object_returns_object_test_for_arguments import processor, shop_shop
#pytest.fixture
def mock_shop_shop():
return Mock(spec=shop_shop)
def test_processor_car_called_with_100(mock_shop_shop):
proc = processor(mock_shop_shop)
proc.run()
assert mock_shop_shop.car_shop.car.drive.assert_called_once_with(100)
assert mock_shop_shop.car_shop.car.drive.call_count == 1
If using just the code shown in the question, you only have to mock car.drive. This could be done for example this way:
from unittest import mock
from object_returns_object_test_for_arguments import processor, shop_shop
#mock.patch('object_returns_object_test_for_arguments.car.drive')
def test_processor_car_called_with_100(drive_mock):
proc = processor(shop_shop())
proc.run()
drive_mock.assert_called_once_with(100)
As I don't know your real code, you may have to mock more stuff.
As an aside: class names in Python are written upper-case, camelcase-style by default.

how to write a unittest for a class?

class Hand:
def __init__(self,cards, total, soft_ace_count):
self.cards = cards
self.total = total
self.soft_ace_count = soft_ace_count
def __str__(self):
return(str(self.cards)+','+str(self.total)+','+str(self.soft_ace_count))
def add_card(self):
self.cards.append(get_card())
self.score()
def is_blackjack(self):
return len(self.cards)==2 and self.total==21
def is_bust(self):
return self.total > 21
def score(self):
self.total=0
self.soft_ace_count=0
for x in self.cards:
if x > 10:
x=10
self.total+=x
#the following code will decide 1 =11 or 1 = 1
if self.total <= 11 and 1 in self.cards:
self.total+=10
self.soft_ace_count+=1
I am trying to write the unittest for this 'Hand' class. Do I need a init setup for this particular unittest?
Here is part of code of my unittest. Thank you so much if anyone could help me with this. I just started in python. Any suggestions would help. Thank you. Neglect the indent
class hand(unittest.TestCase):
def __init__(self):
self.cards=cards
self.total = total
self.soft_ace_count = soft_ace_count
Assuming that you have your class in a module named hand.py, you can place your unittest script in the same directory. It could look like this:
import unittest
from hand import Hand
class hand(unittest.TestCase):
def test_init(self):
cards = []
h = Hand(cards, 0, 0)
self.assertEqual(h.total, 0)
self.assertEqual(h.soft_ace_count, 0)
self.assertEqual(len(h.cards), 0)
# def test_blackjack(self):
#....
# def test_score(self):
#....
# def test_OTHERTHINGS(self):
#.....
if __name__ == '__main__':
unittest.main()
if you name your unittestscript for example unittesthand.py, you can run it via "python unittestscript.py". You will get the following result:
> .
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
The unittest component has executed all methods starting with "test" and executed. In the example the "test_init" method. This is NOT the initialization of the test, but the unittest for the initialization of class Hand.
Now change for example the code to "self.assertEqual(h.total, 1)", and you will see that the unittest component reports an error.
Now you can create additional cases for the other methods.You can also create a base test case with code that is always executed before and after the unittests and so on...

Resources