├── examples ├── __init__.py ├── date_pattern │ ├── README.md │ ├── __init__.py │ ├── tests │ │ ├── __init__.py │ │ └── test_date_pattern.py │ ├── day.py │ ├── year.py │ ├── month.py │ ├── weekday.py │ ├── last_day_in_month.py │ ├── last_weekday_in_month.py │ ├── composite.py │ └── nth_weekday_in_month.py ├── io_utils │ ├── __init__.py │ ├── tests │ │ ├── __init__.py │ │ └── test_io_utils.py │ └── io_utils.py ├── planet_earth │ ├── __init__.py │ ├── tests │ │ ├── __init__.py │ │ └── test_planet_earth.py │ └── population.py ├── multiple_files │ ├── __init__.py │ ├── tests │ │ ├── __init__.py │ │ └── test_multiple_files.py │ └── multiple_files.py ├── write_on_file │ ├── __init__.py │ ├── tests │ │ ├── __init__.py │ │ └── test_file_writer.py │ └── file_writer.py ├── count_lines_from_file │ ├── README.md │ ├── __init__.py │ ├── tests │ │ ├── __init__.py │ │ └── test_file_reader.py │ └── file_reader.py └── mocking_a_method_call │ ├── __init__.py │ ├── tests │ ├── __init__.py │ └── test_get_countries.py │ └── get_countries.py ├── _config.yml ├── .gitignore ├── docs └── en │ ├── README.md │ └── mocking │ ├── advanced │ ├── README.md │ └── patch.md │ ├── README.md │ ├── essentials │ ├── how-to-mock.md │ ├── README.md │ ├── the-mock-object.md │ └── patch.md │ └── examples │ ├── README.md │ ├── mocking-a-method-call.md │ └── reading-writing-on-files.md └── README.md /examples/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/date_pattern/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/io_utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/planet_earth/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /examples/date_pattern/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/io_utils/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/multiple_files/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/write_on_file/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/count_lines_from_file/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/count_lines_from_file/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/date_pattern/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/mocking_a_method_call/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/multiple_files/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/planet_earth/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/write_on_file/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/count_lines_from_file/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/mocking_a_method_call/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .idea/ 3 | *.txt 4 | *.pyc 5 | */__pycache__/ 6 | .coverage -------------------------------------------------------------------------------- /docs/en/README.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | # List of contents 4 | * [Mocking][mocking] 5 | 6 | [mocking]: ./mocking 7 | -------------------------------------------------------------------------------- /examples/write_on_file/file_writer.py: -------------------------------------------------------------------------------- 1 | class FileWriter: 2 | @staticmethod 3 | def write(file_path, content): 4 | with open(file_path, 'w') as file: 5 | file.write(content) 6 | -------------------------------------------------------------------------------- /examples/date_pattern/day.py: -------------------------------------------------------------------------------- 1 | class DayPattern(object): 2 | def __init__(self, day): 3 | self.day = day 4 | 5 | def matches(self, date): 6 | return self.day == date.day 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/date_pattern/year.py: -------------------------------------------------------------------------------- 1 | class YearPattern(object): 2 | def __init__(self, year): 3 | self.year = year 4 | 5 | def matches(self, date): 6 | return self.year == date.year 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/date_pattern/month.py: -------------------------------------------------------------------------------- 1 | class MonthPattern(object): 2 | def __init__(self, month): 3 | self.month = month 4 | 5 | def matches(self, date): 6 | return self.month == date.month 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/date_pattern/weekday.py: -------------------------------------------------------------------------------- 1 | class WeekdayPattern(object): 2 | def __init__(self, weekday): 3 | self.weekday = weekday 4 | 5 | def matches(self, date): 6 | return self.weekday == date.weekday() 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/date_pattern/last_day_in_month.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | 4 | class LastDayInMonthPattern(object): 5 | def matches(self, date): 6 | tomorrow = date + datetime.timedelta(1) 7 | return date.month != tomorrow.month -------------------------------------------------------------------------------- /examples/count_lines_from_file/file_reader.py: -------------------------------------------------------------------------------- 1 | class FileReader: 2 | 3 | @staticmethod 4 | def count_lines(file_path): 5 | with open(file_path, 'r') as _file: 6 | file_content_list = _file.readlines() 7 | print(file_content_list) 8 | return len(file_content_list) 9 | -------------------------------------------------------------------------------- /docs/en/mocking/advanced/README.md: -------------------------------------------------------------------------------- 1 | # Advanced 2 | 3 | In development 4 | 5 | ![Be patient][be-patient] 6 | 7 | ## You may also want to 8 | * See the [examples][examples] 9 | * Go to [basics of mocking][essentials] 10 | 11 | [essentials]: ../essentials 12 | [examples]: ../examples 13 | 14 | [be-patient]: https://media.giphy.com/media/xT9KVmZwJl7fnigeAg/giphy.gif -------------------------------------------------------------------------------- /examples/date_pattern/last_weekday_in_month.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | 4 | class LastWeekdayInMonthPattern(object): 5 | def __init__(self, weekday): 6 | self.weekday = weekday 7 | 8 | def matches(self, date): 9 | next_week = date + datetime.timedelta(7) 10 | return self.weekday == date.weekday() and next_week.month != date.month 11 | -------------------------------------------------------------------------------- /examples/date_pattern/composite.py: -------------------------------------------------------------------------------- 1 | class CompositePattern(object): 2 | def __init__(self): 3 | self.patterns = list() 4 | 5 | def add(self, pattern): 6 | self.patterns.append(pattern) 7 | 8 | def matches(self, date): 9 | for pattern in self.patterns: 10 | if not pattern.matches(date): 11 | return False 12 | return True 13 | -------------------------------------------------------------------------------- /docs/en/mocking/README.md: -------------------------------------------------------------------------------- 1 | # Teach me how to mock!! 2 | 3 | ![Fucking teach me][fking-teach-me] 4 | 5 | You'll need to ask yourself first... How much are you willing to learn how to mock? 6 | 7 | Do you want to: 8 | * be a [master jedi of mocking][advanced] 9 | * just know [the basics][essentials] 10 | * COOODE!! I want [examples][examples] 11 | 12 | [advanced]: ./advanced 13 | [essentials]: ./essentials 14 | [examples]: ./examples 15 | 16 | [fking-teach-me]: https://media.giphy.com/media/26AHPxxnSw1L9T1rW/giphy.gif -------------------------------------------------------------------------------- /docs/en/mocking/advanced/patch.md: -------------------------------------------------------------------------------- 1 | # Patch (advanced) 2 | 3 | In development 4 | 5 | ![Be patient][be-patient] 6 | 7 | ## Go to 8 | * [examples][examples] 9 | * [advanced mocking][advanced] 10 | * [the essentials of mocking][essentials] 11 | * [mocking main menu][mocking-main-menu] 12 | * [summary][summary] 13 | 14 | [advanced]: ../advanced 15 | [examples]: ../examples 16 | [essentials]: ../essentials 17 | [mocking-main-menu]: ../ 18 | [summary]: ../../ 19 | 20 | [be-patient]: https://media.giphy.com/media/xT9KVmZwJl7fnigeAg/giphy.gif 21 | -------------------------------------------------------------------------------- /docs/en/mocking/essentials/how-to-mock.md: -------------------------------------------------------------------------------- 1 | # How to mock 2 | 3 | In development 4 | 5 | ![Be patient][be-patient] 6 | 7 | ## Go to 8 | * [examples][examples] 9 | * [advanced mocking][advanced] 10 | * [the essentials of mocking][essentials] 11 | * [mocking main menu][mocking-main-menu] 12 | * [summary][summary] 13 | 14 | [advanced]: ../advanced 15 | [examples]: ../examples 16 | [essentials]: ../essentials 17 | [mocking-main-menu]: ../ 18 | [summary]: ../../ 19 | 20 | [be-patient]: https://media.giphy.com/media/xT9KVmZwJl7fnigeAg/giphy.gif 21 | -------------------------------------------------------------------------------- /examples/planet_earth/population.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import re 3 | 4 | 5 | class Population: 6 | def __init__(self): 7 | self.population_api_url = 'http://api.population.io/1.0/' 8 | self.countries_url = 'http://api.population.io/1.0/countries' 9 | 10 | def countries(self): 11 | request_response = requests.get(self.countries_url).json() 12 | return request_response['countries'] 13 | 14 | def countries_with_pattern(self, pattern): 15 | return [country for country in self.countries() if re.match(pattern, country)] 16 | 17 | -------------------------------------------------------------------------------- /docs/en/mocking/essentials/README.md: -------------------------------------------------------------------------------- 1 | # Essentials 2 | 3 | * [Patch][patch] 4 | * [The Mock Object][the-mock-object] 5 | * [How to mock][how-to-mock] 6 | 7 | ## Go to 8 | * [examples][examples] 9 | * [advanced mocking][advanced] 10 | * [the essentials of mocking][essentials] 11 | * [mocking main menu][mocking-main-menu] 12 | * [summary][summary] 13 | 14 | [advanced]: ../advanced 15 | [examples]: ../examples 16 | [essentials]: ../essentials 17 | [mocking-main-menu]: ../ 18 | [summary]: ../../ 19 | 20 | [patch]: ./patch.md 21 | [how-to-mock]: ./how-to-mock.md 22 | [the-mock-object]: ./the-mock-object.md -------------------------------------------------------------------------------- /docs/en/mocking/examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | * [Reading / Writing on Files][reading-writing-on-files] 4 | * [Mocking a method call][mocking-a-method-call] 5 | 6 | ## Go to 7 | * [examples][examples] 8 | * [advanced mocking][advanced] 9 | * [the essentials of mocking][essentials] 10 | * [mocking main menu][mocking-main-menu] 11 | * [summary][summary] 12 | 13 | [advanced]: ../advanced 14 | [examples]: ../examples 15 | [essentials]: ../essentials 16 | [mocking-main-menu]: ../ 17 | [summary]: ../../ 18 | 19 | [reading-writing-on-files]: ./reading-writing-on-files.md 20 | [mocking-a-method-call]: ./mocking-a-method-call.md 21 | -------------------------------------------------------------------------------- /examples/io_utils/io_utils.py: -------------------------------------------------------------------------------- 1 | def write_from_file(file, content): 2 | file.write(content) 3 | 4 | 5 | def write_from_file_path(file_path, content): 6 | with open(file_path, 'a') as _file: 7 | _file.write(content) 8 | 9 | 10 | def get_content_from_file_path(file_path): 11 | with open(file_path, 'r') as _file: 12 | return _file.read() 13 | 14 | 15 | def return_file_with_extra_text(file_path, extra): 16 | with open(file_path, 'r') as _file: 17 | file_content = _file.read() 18 | return add_extra(file_content, extra) 19 | 20 | 21 | def add_extra(content, extra): 22 | return content + extra 23 | -------------------------------------------------------------------------------- /examples/date_pattern/nth_weekday_in_month.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | 4 | class NthWeekdayInMonthPattern(object): 5 | def __init__(self, n, weekday): 6 | self.n = n 7 | self.weekday = weekday 8 | 9 | def matches(self, date): 10 | if self.weekday != date.weekday(): 11 | return False 12 | 13 | return self.n == self._get_weekday_number(date) 14 | 15 | @staticmethod 16 | def _get_weekday_number(date): 17 | n = 1 18 | while True: 19 | previous_date = date - datetime.timedelta(7 * n) 20 | if previous_date.month == date.month: 21 | n += 1 22 | else: 23 | break 24 | return n -------------------------------------------------------------------------------- /examples/write_on_file/tests/test_file_writer.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from unittest.mock import patch, mock_open 3 | 4 | from examples.write_on_file.file_writer import FileWriter 5 | 6 | 7 | class TestFileWriter(unittest.TestCase): 8 | def test_file_writer(self): 9 | fake_file_path = "fake/file/path" 10 | content = "Message to write on file to be written" 11 | with patch('examples.write_on_file.file_writer.open', mock_open()) as mocked_file: 12 | FileWriter().write(fake_file_path, content) 13 | 14 | # assert if opened file on write mode 'w' 15 | mocked_file.assert_called_once_with(fake_file_path, 'w') 16 | 17 | # assert if the specific content was written in file 18 | mocked_file().write.assert_called_once_with(content) 19 | -------------------------------------------------------------------------------- /examples/mocking_a_method_call/get_countries.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | 4 | def get_countries(countries_url='http://api.population.io/1.0/countries'): 5 | request_response = requests.get(countries_url).json() 6 | return request_response['countries'] 7 | 8 | # 9 | # def filter_items(items, pattern): 10 | # return [country for country in items if re.match(pattern, country)] 11 | # 12 | # 13 | # def filter_countries(pattern="B*"): 14 | # print('Filtering countries with the pattern %s' % pattern) 15 | # countries = get_countries() 16 | # filtered_countries = filter_items(countries, pattern) 17 | # print('Countries: %s' % str(filtered_countries)) 18 | # 19 | # if __name__ == '__main__': 20 | # pattern_input = input('Regex pattern to filter countries: ') 21 | # filter_countries(pattern_input) 22 | -------------------------------------------------------------------------------- /examples/count_lines_from_file/tests/test_file_reader.py: -------------------------------------------------------------------------------- 1 | 2 | import unittest 3 | from unittest.mock import patch, mock_open 4 | 5 | from examples.count_lines_from_file.file_reader import FileReader 6 | 7 | 8 | class TestReadFiles(unittest.TestCase): 9 | def test_count_lines(self): 10 | file_content_mock = """Hello World!! 11 | Hello World is in a file. 12 | A mocked file. 13 | He is not real. 14 | But he think he is. 15 | He doesn't know he is mocked""" 16 | fake_file_path = 'file/path/mock' 17 | 18 | with patch('examples.count_lines_from_file.file_reader.open'.format(__name__), 19 | new=mock_open(read_data=file_content_mock)) as _file: 20 | actual = FileReader().count_lines(fake_file_path) 21 | _file.assert_called_once_with(fake_file_path, 'r') 22 | 23 | expected = len(file_content_mock.split('\n')) 24 | self.assertEqual(expected, actual) -------------------------------------------------------------------------------- /examples/multiple_files/multiple_files.py: -------------------------------------------------------------------------------- 1 | import chardet 2 | 3 | 4 | class FileUtils: 5 | def __init__(self, file_path=None): 6 | self.file_path = file_path 7 | 8 | def split_in_two(self, line_limit, file_path_2=None, file_path_3=None): 9 | file_path_2 = file_path_2 or '/tmp/file_path_2' 10 | file_path_3 = file_path_3 or '/tmp/file_path_3' 11 | 12 | with open(self.file_path, 'r', encoding=self.file_encoding) as source, \ 13 | open(file_path_2, 'w', encoding=self.file_encoding) as file_2, \ 14 | open(file_path_3, 'w', encoding=self.file_encoding) as file_3: 15 | for index, line in enumerate(source): 16 | if index < line_limit: 17 | file_2.write(line) 18 | else: 19 | file_3.write(line) 20 | 21 | return file_path_2, file_path_3 22 | 23 | @property 24 | def file_encoding(self): 25 | return self.get_file_encoding(self.file_path) 26 | 27 | @staticmethod 28 | def get_file_encoding(file_path): 29 | with open(file_path, 'rb') as encoding_file: 30 | return chardet.detect(encoding_file.read(1024)).get('encoding', 'UTF-8') 31 | -------------------------------------------------------------------------------- /examples/planet_earth/tests/test_planet_earth.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from unittest.mock import patch 3 | 4 | from examples.planet_earth.population import Population 5 | 6 | 7 | class TestPlanetEarth(unittest.TestCase): 8 | @patch('requests.get') 9 | def test_get_countries(self, mock_get): 10 | expected = ['Brazil', 'The rest'] 11 | mock_get.return_value.json.return_value = { 12 | 'countries': expected 13 | } 14 | 15 | planet_earth = Population() 16 | actual = planet_earth.countries() 17 | 18 | mock_get.assert_called_once_with(planet_earth.countries_url) 19 | self.assertEqual(expected, actual) 20 | 21 | @patch('requests.get') 22 | def test_get_countries_with_pattern(self, mock_get): 23 | countries = ['Brazil', 'The rest', 'Some other country', 'Balululap'] 24 | mock_get.return_value.json.return_value = {'countries': countries} 25 | expected = ['Brazil', 'Balululap'] 26 | 27 | planet_earth = Population() 28 | actual = planet_earth.countries_with_pattern('(Ba)|(Br)') 29 | 30 | mock_get.assert_called_once_with(planet_earth.countries_url) 31 | 32 | self.assertEqual(expected, actual) 33 | -------------------------------------------------------------------------------- /examples/multiple_files/tests/test_multiple_files.py: -------------------------------------------------------------------------------- 1 | import os 2 | import tempfile 3 | import unittest 4 | 5 | from examples.multiple_files.multiple_files import FileUtils 6 | 7 | 8 | class TestSplitFileInTwo(unittest.TestCase): 9 | def setUp(self): 10 | self.file_path_1 = tempfile.mkstemp()[1] 11 | self.file_path_2 = tempfile.mkstemp()[1] 12 | self.file_path_3 = tempfile.mkstemp()[1] 13 | 14 | self.expected_content_file_2 = 'line 1\nline 2\n' 15 | self.expected_content_file_3 = 'line 3\nline 4\nline 5\n' 16 | self.content = self.expected_content_file_2 + self.expected_content_file_3 17 | 18 | file_1 = open(self.file_path_1, 'w') 19 | file_1.write(self.content) 20 | file_1.close() 21 | 22 | def tearDown(self): 23 | os.remove(self.file_path_1) 24 | os.remove(self.file_path_2) 25 | os.remove(self.file_path_3) 26 | 27 | def test_split_in_two_should_have_expected_content(self): 28 | """Asserts if returned files has the expected content""" 29 | 30 | actual_file_path_2, actual_file_path_3 = \ 31 | FileUtils(self.file_path_1).split_in_two(2, self.file_path_2, self.file_path_3) 32 | 33 | with open(self.file_path_2, 'r') as file: 34 | file_2_content = file.read() 35 | 36 | with open(self.file_path_3, 'r') as file: 37 | file_3_content = file.read() 38 | 39 | self.assertEqual(self.expected_content_file_2, file_2_content) 40 | self.assertEqual(self.expected_content_file_3, file_3_content) 41 | self.assertEqual((self.file_path_2, self.file_path_3), (actual_file_path_2, actual_file_path_3)) 42 | -------------------------------------------------------------------------------- /docs/en/mocking/essentials/the-mock-object.md: -------------------------------------------------------------------------------- 1 | # The Mock Object 2 | 3 | ### Defining a Mock Object 4 | 5 | Take a close look at this code. 6 | 7 | Try to predict what will happen. 8 | 9 | ```` python 10 | from unittest.mock import Mock 11 | mock = Mock() 12 | mock.return_value = 3 13 | mock() 14 | mock.assert_called_once_with() 15 | mock() 16 | mock.assert_called_once_with() 17 | ```` 18 | 19 | **Let's read this code together now:** 20 | 21 | * In the line `mock = Mock()`, we are instantiating the mock object. 22 | 23 | * In the line `mock.return_value = 3`, we are assigning a return value for this instance of the object. 24 | That means that every time I call `mock()` (the instance of `Mock()`), I'll get a value of `3`. 25 | 26 | * Look at `mock.assert_called_once_with()`. Here we are asserting that we called the method with no parameters once. 27 | An **AssertionError** will be raised **if this assertion fails**. This happens on the second time we call `mock.assert_called_once_with()`. 28 | 29 | **Now you've just learned to assert mocked method calls** 30 | 31 | ### Mapping method to be mocked by Mock 32 | 33 | In development 34 | 35 | ![Be patient][be-patient] 36 | 37 | ### Mapping exceptions 38 | 39 | In development 40 | 41 | ![Be patient][be-patient] 42 | 43 | ### More on side-effect 44 | 45 | In development 46 | 47 | ![Be patient][be-patient] 48 | 49 | ## Go to 50 | * [examples][examples] 51 | * [advanced mocking][advanced] 52 | * [the essentials of mocking][essentials] 53 | * [mocking main menu][mocking-main-menu] 54 | * [summary][summary] 55 | 56 | [advanced]: ../advanced 57 | [examples]: ../examples 58 | [essentials]: ../essentials 59 | [mocking-main-menu]: ../ 60 | [summary]: ../../ 61 | 62 | [be-patient]: https://media.giphy.com/media/xT9KVmZwJl7fnigeAg/giphy.gif 63 | -------------------------------------------------------------------------------- /examples/io_utils/tests/test_io_utils.py: -------------------------------------------------------------------------------- 1 | import tempfile 2 | import unittest 3 | from unittest.mock import patch, mock_open 4 | 5 | from examples.io_utils import io_utils 6 | 7 | try: 8 | from StringIO import StringIO 9 | except ImportError: 10 | from io import StringIO 11 | 12 | 13 | class TestIoUtils(unittest.TestCase): 14 | def test_get_content_from_file_path(self): 15 | expected = 'banana' 16 | file_path = '' 17 | with patch('examples.io_utils.io_utils.open', mock_open(read_data=expected)) as m: 18 | actual = io_utils.get_content_from_file_path(file_path) 19 | m.assert_called_once_with(file_path, 'r') 20 | 21 | self.assertEqual(expected, actual) 22 | 23 | def test_return_file_with_extra_text(self): 24 | extra = "\nIMA mothefucking motherfucka" 25 | file_content = "banana" 26 | expected = file_content + extra 27 | file_path = 'motherfucking/file/path' 28 | with patch('examples.io_utils.io_utils.open', mock_open(read_data=file_content)) as file: 29 | actual = io_utils.return_file_with_extra_text(file_path, extra) 30 | file.assert_called_once_with(file_path, 'r') 31 | 32 | self.assertEqual(expected, actual) 33 | 34 | 35 | class TestReadContent(unittest.TestCase): 36 | def setUp(self): 37 | self.message = 'Hello world' 38 | 39 | def test_write_from_file_path(self): 40 | file_path = tempfile.mkstemp()[1] 41 | expected = self.message 42 | io_utils.write_from_file_path(file_path, expected) 43 | actual = io_utils.get_content_from_file_path(file_path) 44 | 45 | self.assertEqual(expected, actual) 46 | 47 | def test_write_from_file(self): 48 | file = StringIO() 49 | expected = self.message 50 | io_utils.write_from_file(file, expected) 51 | file.seek(0) 52 | actual = file.read() 53 | self.assertEqual(expected, actual) 54 | 55 | -------------------------------------------------------------------------------- /examples/mocking_a_method_call/tests/test_get_countries.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from unittest.mock import patch 3 | 4 | from examples.mocking_a_method_call.get_countries import get_countries 5 | 6 | 7 | class TestGetCountriesWithPatchAsDecorator(unittest.TestCase): 8 | @patch('examples.mocking_a_method_call.get_countries.requests.get') 9 | def test_get_countries(self, mock_http_get): 10 | expected_countries = ['Brazil', 'The rest of the world'] 11 | fake_url = 'http://ima.fake.url' 12 | 13 | mock_http_get_response = mock_http_get.return_value 14 | mock_http_get_response.json.return_value = { 15 | 'countries': expected_countries 16 | } 17 | 18 | actual_countries = get_countries(fake_url) 19 | 20 | mock_http_get.assert_called_once_with(fake_url) 21 | 22 | self.assertEqual(expected_countries, actual_countries) 23 | 24 | 25 | class TestGetCountriesWithStraightForward(unittest.TestCase): 26 | def setUp(self): 27 | patcher = patch('examples.mocking_a_method_call.get_countries.requests.get') 28 | self.mock_http_get = patcher.start() 29 | self.addCleanup(patch.stopall) 30 | 31 | def test_get_countries(self): 32 | expected_countries = ['Brazil', 'The rest of the world'] 33 | fake_url = 'http://ima.fake.url' 34 | 35 | mock_http_get_response = self.mock_http_get.return_value 36 | mock_http_get_response.json.return_value = { 37 | 'countries': expected_countries 38 | } 39 | 40 | actual_countries = get_countries(fake_url) 41 | 42 | self.mock_http_get.assert_called_once_with(fake_url) 43 | 44 | self.assertEqual(expected_countries, actual_countries) 45 | 46 | 47 | class TestGetCountriesWithContextManagerPatch(unittest.TestCase): 48 | def test_get_countries(self): 49 | with patch('examples.mocking_a_method_call.get_countries.requests.get') as mock_http_get: 50 | expected_countries = ['Brazil', 'The rest of the world'] 51 | fake_url = 'http://ima.fake.url' 52 | 53 | mock_http_get_response = mock_http_get.return_value 54 | mock_http_get_response.json.return_value = { 55 | 'countries': expected_countries 56 | } 57 | 58 | actual_countries = get_countries(fake_url) 59 | 60 | mock_http_get.assert_called_once_with(fake_url) 61 | 62 | self.assertEqual(expected_countries, actual_countries) 63 | -------------------------------------------------------------------------------- /docs/en/mocking/essentials/patch.md: -------------------------------------------------------------------------------- 1 | # Patch 2 | 3 | In simple words, `patch()` will temporarily change the object referenced with another one (*default: MagicMock object*). 4 | 5 | Don't know what a MagicMock or a mock object is? 6 | Come [here][mock-object] for a brief explanation of the mock object. 7 | 8 | Patch can be used in three ways: 9 | * Context manager 10 | * Decorator 11 | * Straight-forward 12 | 13 | ## Example 14 | 15 | ```` python 16 | def foo(): 17 | pass 18 | ```` 19 | 20 | ### Patch as context manager 21 | 22 | ```` python 23 | from unittest.mock import patch 24 | 25 | # __main__.foo is the reference of method foo 26 | with patch('__main__.foo') as foo_mock: 27 | foo_mock.return_value = 3 28 | print(foo_mock()) 29 | print(foo()) 30 | 31 | ```` 32 | 33 | ### Patch as decorator of another function 34 | 35 | ```` python 36 | from unittest.mock import patch 37 | 38 | # __main__.foo is the reference of method foo 39 | @patch('__main__.foo') 40 | def fake_mock(foo_mock): 41 | foo_mock.return_value = 3 42 | print(foo_mock()) 43 | print(foo()) 44 | 45 | fake_mock() 46 | ```` 47 | 48 | ### Straight-forward patch 49 | 50 | ```` python 51 | from unittest.mock import patch 52 | 53 | # __main__.foo is the reference of method foo 54 | patcher = patch('__main__.foo') 55 | foo_mock = patcher.start() 56 | foo_mock.return_value = 3 57 | print(foo_mock()) 58 | print(foo()) 59 | patcher.stop() 60 | ```` 61 | 62 | ## Parameter *Target* 63 | 64 | It's time to understand the **\_\_main\_\_.foo** of `patch('__main__.foo')` from the example above. 65 | 66 | This is where the object is looked up. More details in the official doc on [where to patch][] 67 | 68 | I believe this is enough for most of your tests. 69 | Wanna know more about patch? Come to the [advanced patch tutorial][advanced-patch] 70 | 71 | ![Be patient][be-patient] 72 | 73 | ## Go to 74 | * [examples][examples] 75 | * [advanced mocking][advanced] 76 | * [the essentials of mocking][essentials] 77 | * [mocking main menu][mocking-main-menu] 78 | * [summary][summary] 79 | 80 | [official-documentation-where-to-patch]: https://docs.python.org/3/library/unittest.mock.html#id5 81 | 82 | [advanced]: ../advanced 83 | [examples]: ../examples 84 | [essentials]: ../essentials 85 | [mocking-main-menu]: ../ 86 | [summary]: ../../ 87 | 88 | [advanced-patch]: ../advanced/patch.md 89 | [mock-object]: ./the-mock-object.md 90 | 91 | [be-patient]: https://media.giphy.com/media/xT9KVmZwJl7fnigeAg/giphy.gif 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unit Testing with Python 3 2 | 3 | ## Summary 4 | 5 | ### Examples 6 | #### Mocking 7 | * [Reading / Writing on Files][reading-writing-on-files] 8 | * [Mocking a method call][mocking-a-method-call] 9 | 10 | ### Tutorials 11 | #### Mocking 12 | * **[Essentials][essentials]** 13 | * [Patch][patch] 14 | * [The Mock Object][the-mock-object] 15 | * [How to mock][how-to-mock] 16 | 17 | * **[Jedi master level of mocking][advanced]** 18 | #### Coverage 19 | 20 | Just do this... 21 | ```` bash 22 | # coverage run --source=. -m unittest discover 23 | # coverage report -m 24 | python -m coverage run --source=. -m unittest discover 25 | # generate an inline report 26 | python -m coverage report -m 27 | # generates an html page 28 | python -m coverage html -d coverage_html 29 | 30 | # your html result will be located in coverage_html/index.html 31 | ```` 32 | ---- 33 | 34 | ## DAFUCK IS A TEST? 35 | 36 | If you don't fucking know what is a test, let me tell ya. 37 | 38 | > A test is a [specification] for program. 39 | 40 | cool.. 41 | 42 | ## What about Unit Testing 43 | 44 | The same as for the test, dãã. But for a program unity. For a method, if you wish. 45 | 46 | > My method 47 | 48 | ```` python 49 | def who(ze_bula): 50 | return "My {}".format(ze_bula) 51 | ```` 52 | 53 | > Can be tested with 54 | 55 | 56 | ```` python 57 | import unittest 58 | 59 | class TestWho(unittest.TestCase): 60 | def test_who(self): 61 | expected = "My BotoFe" 62 | actual = who("BotoFe") 63 | self.assertEqual(expected, actual) 64 | ```` 65 | 66 | Does the code makes fucking sense? 67 | No fucking way!! 68 | 69 | But I guarantee to you that is tested. 70 | 71 | ## Mocks? 72 | 73 | Yeah!! 74 | 75 | It's awesome aswell. 76 | 77 | You don't need to call private functions that does horrifyingly horrifying things that you don't care. 78 | You can mock them. 79 | 80 | Let's say I have a method called `mama()`. `mama()` does a lot of things. `mama()` calls a weird function called `weirdo()` 81 | 82 | ```` python 83 | # weirdo() is a method you will want to mock 84 | def weirdo(a_string): 85 | # Do a bunch of bunch of bunches of things 86 | # Do more things 87 | # Opens a fucking file 88 | # OMG. weirdo() is making 500 HTTP requests 89 | # weirdo is being weirdo 90 | return a_string 91 | 92 | # mama() is a method you will want to test 93 | def mama(): 94 | i_am_a_variable = "IMA STRING" 95 | weirdo_response = weirdo(i_am_a_variable) 96 | return weirdo_response + '1' 97 | ```` 98 | 99 | And let's say, for the sake of this tutorial that probably only I will read, that you are testing `mama()` and you want to fake a return of `weirdo()`. 100 | 101 | Is it possible? 102 | 103 | Yeah, it is. Look at this: 104 | 105 | ```` python 106 | 107 | import unittest 108 | from unittest.mock import patch 109 | 110 | class TestMama(unittest.TestCase): 111 | 112 | @patch('{}.weirdo'.format(__name__)) 113 | def test_mama(self, mock_weirdo): 114 | mock_weirdo.return_value = "IMA weirdo" 115 | 116 | expected = "IMA weirdo1" 117 | actual = mama() 118 | 119 | self.assertEqual(expected, actual) 120 | 121 | ```` 122 | OMG.. What did happened here? 123 | 124 | Did you see this `patch()`? WTF? 125 | Could you understand what he is doing? 126 | Does it make **sense**? 127 | Did you like it? 128 | Did you love it? 129 | 130 | No matter if you like it or not, testing is essential. 131 | And tests generally involves mocking methods. 132 | 133 | So if you make programs with Python and you don't know how to test your code, i recommend to you to start learning. 134 | 135 | **It's necessary.** 136 | 137 | ## How can I learn testing? 138 | 139 | Come here. Come closer. 140 | 141 | **[I'll teach you how.][summary]** 142 | 143 | ![gif-come-here] 144 | 145 | My plans with this repository is to to teach ~~you~~ me how to master on python fucking testing. 146 | 147 | 148 | ## Credits 149 | * **date_pattern package:** http://www.onlamp.com/pub/a/python/2005/02/03/tdd_pyunit2.html?page=3 150 | * **mock:** https://docs.python.org/3/library/unittest.mock.html 151 | * **mock.assert_\*:** https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock.assert_called 152 | * **testing with python:** https://donkirkby.github.io/testing/ 153 | 154 | 155 | [specification]: http://langrsoft.com/2006/06/05/are-tests-specs/ 156 | [gif-come-here]: https://media.giphy.com/media/3ohA2VpfGovSNE8ESI/giphy.gif 157 | 158 | [summary]: ./docs/en/ 159 | 160 | [be-patient]: https://media.giphy.com/media/xT9KVmZwJl7fnigeAg/giphy.gif 161 | 162 | [reading-writing-on-files]: ./docs/en/mocking/examples/reading-writing-on-files.md 163 | [mocking-a-method-call]: ./docs/en/mocking/examples/mocking-a-method-call.md 164 | [essentials]: ./docs/en/mocking/essentials 165 | [advanced]: ./docs/en/mocking/advanced 166 | 167 | 168 | [patch]: ./docs/en/mocking/essentials/patch.md 169 | [how-to-mock]: ./docs/en/mocking/essentials/how-to-mock.md 170 | [the-mock-object]: ./docs/en/mocking/essentials/the-mock-object.md -------------------------------------------------------------------------------- /examples/date_pattern/tests/test_date_pattern.py: -------------------------------------------------------------------------------- 1 | # failUnless = assertTrue 2 | # failIf = assertFalse 3 | 4 | import datetime 5 | import unittest 6 | 7 | from examples.date_pattern.composite import CompositePattern 8 | from examples.date_pattern.day import DayPattern 9 | from examples.date_pattern.last_day_in_month import LastDayInMonthPattern 10 | from examples.date_pattern.last_weekday_in_month import LastWeekdayInMonthPattern 11 | from examples.date_pattern.month import MonthPattern 12 | from examples.date_pattern.nth_weekday_in_month import NthWeekdayInMonthPattern 13 | from examples.date_pattern.weekday import WeekdayPattern 14 | from examples.date_pattern.year import YearPattern 15 | 16 | MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY = range(0, 7) 17 | 18 | 19 | class YearPatternTests(unittest.TestCase): 20 | def setUp(self): 21 | self.date = datetime.date(2004, 9, 29) 22 | 23 | def test_year_pattern_matches(self): 24 | year_pattern = YearPattern(2004) 25 | self.assertTrue(year_pattern.matches(self.date)) 26 | 27 | def test_year_pattern_does_not_match(self): 28 | year_pattern = YearPattern(2005) 29 | self.assertFalse(year_pattern.matches(self.date)) 30 | 31 | 32 | class MonthPatternTests(unittest.TestCase): 33 | def setUp(self): 34 | self.date = datetime.date(2004, 9, 29) 35 | 36 | def test_month_pattern_matches(self): 37 | month_pattern = MonthPattern(9) 38 | self.assertTrue(month_pattern.matches(self.date)) 39 | 40 | def test_month_pattern_does_not_match(self): 41 | month_pattern = MonthPattern(11) 42 | self.assertFalse(month_pattern.matches(self.date)) 43 | 44 | 45 | class DayPatternTests(unittest.TestCase): 46 | def setUp(self): 47 | self.date = datetime.date(2004, 9, 29) 48 | 49 | def test_day_pattern_matches(self): 50 | day_pattern = DayPattern(29) 51 | self.assertTrue(day_pattern.matches(self.date)) 52 | 53 | def test_day_pattern_does_not_match(self): 54 | day_pattern = DayPattern(21) 55 | self.assertFalse(day_pattern.matches(self.date)) 56 | 57 | 58 | class WeekdayPatternTests(unittest.TestCase): 59 | def setUp(self): 60 | self.date = datetime.date(2004, 9, 29) 61 | 62 | def test_weekday_pattern_matches(self): 63 | weekday_pattern = WeekdayPattern(2) # Wednesday 64 | self.assertTrue(weekday_pattern.matches(self.date)) 65 | 66 | def test_weekday_pattern_does_not_match(self): 67 | weekday_pattern = WeekdayPattern(1) # Tuesday 68 | self.assertFalse(weekday_pattern.matches(self.date)) 69 | 70 | 71 | class CompositePatternTests(unittest.TestCase): 72 | def setUp(self): 73 | self.date = datetime.date(2004, 9, 29) 74 | 75 | def test_composite_matches(self): 76 | cp = CompositePattern() 77 | cp.add(YearPattern(2004)) 78 | cp.add(MonthPattern(9)) 79 | cp.add(DayPattern(29)) 80 | self.assertTrue(cp.matches(self.date)) 81 | 82 | def test_composite_does_not_match(self): 83 | cp = CompositePattern() 84 | cp.add(YearPattern(2004)) 85 | cp.add(MonthPattern(9)) 86 | cp.add(DayPattern(30)) 87 | self.assertFalse(cp.matches(self.date)) 88 | 89 | def test_composite_without_year_matches(self): 90 | cp = CompositePattern() 91 | cp.add(MonthPattern(9)) 92 | cp.add(DayPattern(29)) 93 | self.assertTrue(cp.matches(self.date)) 94 | 95 | 96 | class LastWeekdayPatternTests(unittest.TestCase): 97 | def setUp(self): 98 | self.pattern = LastWeekdayInMonthPattern(WEDNESDAY) 99 | 100 | def test_last_wednesday_matches(self): 101 | last_wed_of_sept_2004 = datetime.date(2004, 9, 29) 102 | self.assertTrue(self.pattern.matches(last_wed_of_sept_2004)) 103 | 104 | def test_last_wednesday_does_not_match(self): 105 | first_wed_of_sept_2004 = datetime.date(2004, 9, 1) 106 | self.assertFalse(self.pattern.matches(first_wed_of_sept_2004)) 107 | 108 | 109 | class NthWeekdayPatternTests(unittest.TestCase): 110 | def setUp(self): 111 | self.pattern = NthWeekdayInMonthPattern(1, WEDNESDAY) 112 | 113 | def test_matches(self): 114 | first_wed_of_sept_2004 = datetime.date(2004, 9, 1) 115 | self.assertTrue(self.pattern.matches(first_wed_of_sept_2004)) 116 | 117 | def test_does_not_matches(self): 118 | second_wed_of_sept_2004 = datetime.date(2004, 9, 8) 119 | self.assertFalse(self.pattern.matches(second_wed_of_sept_2004)) 120 | 121 | 122 | class LastDayInMonthPatternTests(unittest.TestCase): 123 | def setUp(self): 124 | self.pattern = LastDayInMonthPattern() 125 | 126 | def test_matches(self): 127 | last_day_in_sept_2004 = datetime.date(2004, 9, 30) 128 | self.assertTrue(self.pattern.matches(last_day_in_sept_2004)) 129 | 130 | def test_does_not_match(self): 131 | second_to_day_in_sept_2004 = datetime.date(2004, 9, 23) 132 | self.assertFalse(self.pattern.matches(second_to_day_in_sept_2004)) 133 | 134 | 135 | if __name__ == '__main__': unittest.main() 136 | -------------------------------------------------------------------------------- /docs/en/mocking/examples/mocking-a-method-call.md: -------------------------------------------------------------------------------- 1 | # Mocking a method call 2 | 3 | Imagine a method that makes an **HTTP request**. Let's call him **MethodX**. 4 | 5 | **MethodX** is being called from another method. Guess what.. this another method will be called **MethodY**. 6 | 7 | If we are testing **MethodY**, can we mock **MethodX** so our **test doesn't make the HTTP request**? 8 | 9 | ![Of Course][of-course-gif] 10 | 11 | In this example we are going to mock a **GET HTTP request** that fetches a list of countries from a [world population API][population-api]. 12 | 13 | Also, we will assert if our method is calling the right url `http://api.population.io/1.0/countries`. 14 | 15 | With that in mind, we ~~disobey [TDD][wikipedia-TDD]~~ write our method to be tested before the test itself: 16 | 17 | ```` python 18 | def get_countries(): 19 | countries_url = 'http://api.population.io/1.0/countries' 20 | request_response = requests.get(countries_url).json() 21 | return request_response['countries'] 22 | ```` 23 | 24 | Look at this filthy piece of code: `requests.get(countries_url).json()`. 25 | 26 | This is where the HTTP request will be made. 27 | 28 | ## How can we mock it? 29 | 30 | ### Using patch 31 | 32 | #### Using patch as decorator 33 | You will use the `@patch('package.module.method')` decorator just before the test method. 34 | This will create the mock that will be passed as parameter to your test function. 35 | 36 | ```` python 37 | import unittest 38 | from unittest.mock import patch 39 | 40 | from examples.mocking_a_method_call.get_countries import get_countries 41 | 42 | 43 | class TestGetCountriesWithPatchAsDecorator(unittest.TestCase): 44 | @patch('examples.mocking_a_method_call.get_countries.requests.get') 45 | def test_get_countries(self, mock_http_get): 46 | expected_countries = ['Brazil', 'The rest of the world'] 47 | fake_url = 'http://ima.fake.url' 48 | 49 | mock_http_get_response = mock_http_get.return_value 50 | mock_http_get_response.json.return_value = { 51 | 'countries': expected_countries 52 | } 53 | 54 | actual_countries = get_countries(fake_url) 55 | 56 | mock_http_get.assert_called_once_with(fake_url) 57 | 58 | self.assertEqual(expected_countries, actual_countries) 59 | ```` 60 | #### Using a straight-forward patch 61 | ```` python 62 | 63 | class TestGetCountriesWithStraightForward(unittest.TestCase): 64 | def setUp(self): 65 | patcher = patch('examples.mocking_a_method_call.get_countries.requests.get') 66 | self.mock_http_get = patcher.start() 67 | self.addCleanup(patch.stopall) 68 | 69 | def test_get_countries(self): 70 | expected_countries = ['Brazil', 'The rest of the world'] 71 | fake_url = 'http://ima.fake.url' 72 | 73 | mock_http_get_response = self.mock_http_get.return_value 74 | mock_http_get_response.json.return_value = { 75 | 'countries': expected_countries 76 | } 77 | 78 | actual_countries = get_countries(fake_url) 79 | 80 | self.mock_http_get.assert_called_once_with(fake_url) 81 | 82 | self.assertEqual(expected_countries, actual_countries) 83 | ```` 84 | #### Using patch as context manager 85 | ```` python 86 | class TestGetCountriesWithContextManagerPatch(unittest.TestCase): 87 | def test_get_countries(self): 88 | with patch('examples.mocking_a_method_call.get_countries.requests.get') as mock_http_get: 89 | expected_countries = ['Brazil', 'The rest of the world'] 90 | fake_url = 'http://ima.fake.url' 91 | 92 | mock_http_get_response = mock_http_get.return_value 93 | mock_http_get_response.json.return_value = { 94 | 'countries': expected_countries 95 | } 96 | 97 | actual_countries = get_countries(fake_url) 98 | 99 | mock_http_get.assert_called_once_with(fake_url) 100 | 101 | self.assertEqual(expected_countries, actual_countries) 102 | ```` 103 | 104 | See the [source code][mocking-a-method-call-source-code] 105 | 106 | ### So many ways to mock... 107 | 108 | Yeah, there is. 109 | 110 | Actually, all of this [wibbly wobbly timey wimey][wibbly-wobbly-timey-wimey] stuff is centered on the **unittest Mock object**. 111 | 112 | Further details [here][how-to-mock]. 113 | 114 | Also, see the [official docs][official-documentation-mock-object] 115 | 116 | ## What about now? 117 | * See other [examples][examples] 118 | * Go to [advanced mocking][advanced] 119 | * Go to [the essentials of mocking][essentials] 120 | * Go to [mocking main menu][mocking-main-menu] 121 | * Go to [summary][summary] 122 | 123 | 124 | [official-documentation-mock-object]: https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock 125 | [official-documentation-nesting-patch-decorators]: https://docs.python.org/3/library/unittest.mock.html#nesting-patch-decorators 126 | 127 | [wikipedia-TDD]: https://pt.wikipedia.org/wiki/Test_Driven_Development 128 | 129 | [population-api]: http://api.population.io/ 130 | 131 | [advanced]: ../advanced 132 | [examples]: ../examples 133 | [essentials]: ../essentials 134 | [mocking-main-menu]: ../ 135 | [summary]: ../../ 136 | 137 | [how-to-mock]: ../essentials/how-to-mock.md 138 | [the-mock-object]: ../essentials/the-mock-object.md 139 | 140 | [mocking-a-method-call-source-code]: https://github.com/otrabalhador/python-testing-by-examples/tree/master/examples/mocking_a_method_call 141 | 142 | [wait-gif]: https://media.giphy.com/media/xT9KVmZwJl7fnigeAg/giphy.gif 143 | [of-course-gif]: https://media.giphy.com/media/l41YfdYdptDB9RHIA/giphy.gif 144 | [wibbly-wobbly-timey-wimey]: https://www.youtube.com/watch?v=q2nNzNo_Xps 145 | -------------------------------------------------------------------------------- /docs/en/mocking/examples/reading-writing-on-files.md: -------------------------------------------------------------------------------- 1 | # Reading / Writing on Files 2 | 3 | ## Topics on this page: 4 | * [Reading Files][reading-files] 5 | * [Writing on Files][writing-on-files] 6 | 7 | ## Reading Files 8 | 9 | Let's say I have a method that returns the number of lines from a file. 10 | 11 | 12 | ```` python 13 | class FileReader: 14 | 15 | @staticmethod 16 | def count_lines(file_path): 17 | with open(file_path, 'r') as _file: 18 | file_content_list = _file.readlines() 19 | print(file_content_list) 20 | return len(file_content_list) 21 | 22 | ```` 23 | 24 | But I don't want to open a real file. I wanna **mock the opening** and **the content** of the file. 25 | This is *elegantly possible* with [`mock_open()`][official-documentation-mock-open]. 26 | 27 | This replace the use of `open()`. 28 | 29 | This works with both `open()` called directly like this: 30 | ```` python 31 | file = open('file/path', r) 32 | ```` 33 | 34 | and with a context manager like this: 35 | ```` python 36 | with open('file/path', r) as _file: 37 | # ... 38 | ```` 39 | 40 | ### Using mock_open 41 | *See the [official documentation][official-documentation-mock-open] for further detail.* 42 | 43 | We are going to use **patch** with **parameter** `new=mock_open()`, so the target is replaced with a **mock_open** object. 44 | 45 | `mock_open()` has a parameter called `read_data` that is a string for the `read()`, `readline()` and `readlines()` methods of the file opened. 46 | 47 | Here is how to mock the file opening and reading with a context manager. 48 | 49 | ```` python 50 | with patch('__main__.open', new=mock_open(read_data='Fooooo')) as _file: 51 | # do your call to path 'foo/bar' 52 | _file.assert_called_once_with('foo/bar', 'r') 53 | ```` 54 | 55 | #### FAQ about this code 56 | 57 | > Whatafuck is a context manager? 58 | > It's basically using the syntax `with ..... as variable:`. 59 | > More details [here][python-context-manager]. 60 | 61 | > Whatafuck is that `__main__.open`? 62 | > That, my friend, is the reference for where the object **open** is being *looked up*. In the example above, however, that `__main__.open` is just for illustration, because I wasn't mocking any specific **open()**. 63 | > More details in the [official doc][official-documentation-where-to-patch]. 64 | 65 | > Whatafuck is that **assert_called_once_with**? 66 | > It makes an assertion if the object was called only one time with the respective parameters. 67 | > Check it out [here][assert_called]. 68 | 69 | I ~~bet my ass~~ am pretty sure that you are fucking ready to test our method `count_lines_from_file()` now. 70 | 71 | So let's check out the test code. 72 | 73 | ```` python 74 | import unittest 75 | from unittest.mock import patch, mock_open 76 | 77 | from examples.count_lines.file_reader import FileReader 78 | 79 | 80 | class TestReadFiles(unittest.TestCase): 81 | def test_count_lines(self): 82 | file_content_mock = """Hello World!! 83 | Hello World is in a file. 84 | A mocked file. 85 | He is not real. 86 | But he think he is. 87 | He doesn't know he is mocked""" 88 | fake_file_path = 'file/path/mock' 89 | 90 | with patch('examples.count_lines.file_reader.open'.format(__name__), 91 | new=mock_open(read_data=file_content_mock)) as _file: 92 | actual = FileReader().count_lines(fake_file_path) 93 | _file.assert_called_once_with(fake_file_path, 'r') 94 | 95 | expected = len(file_content_mock.split('\n')) 96 | self.assertEqual(expected, actual) 97 | ```` 98 | 99 | See the [source code][count-lines-source-code] 100 | 101 | ## Writing on Files 102 | 103 | Let's make the simplest. A method that **receives a message** and **write it on a file** given a specific **file_path**. 104 | 105 | ```` python 106 | class FileWriter: 107 | @staticmethod 108 | def write(file_path, content): 109 | with open(file_path, 'w') as file: 110 | file.write(content) 111 | ```` 112 | 113 | To mock the opening file and writing content on it, we can use [`mock_open()`][official-documentation-mock-open]. 114 | The mock for this object will be similar to what we've previously seen in [Reading Files][reading-files], with the exception that we don't need to pass the parameter `read_data` in `mock_open()` because we are not retrieving data from the file. 115 | 116 | ### How the assertion will look like? 117 | To answer this question, we need to ask ourselves: 118 | > What do we want to test in this function? 119 | 120 | A nice test case, in my delusional opinion, will test if a **specific file_path** was called on `open()`, with a specific **open mode**, and if a **specific content** was **written** in the file. 121 | 122 | The [**Mock**][official-documentation-mock-object] object implements assertions that could help us testing that. 123 | The one that will be useful to us is `MockObject.assert_called_once_with()`. 124 | 125 | Let's take a look on the test? 126 | 127 | ```` python 128 | import unittest 129 | from unittest.mock import patch, mock_open 130 | 131 | from examples.write_on_file.file_writer import FileWriter 132 | 133 | 134 | class TestFileWriter(unittest.TestCase): 135 | def test_file_writer(self): 136 | fake_file_path = "fake/file/path" 137 | content = "Message to write on file to be written" 138 | with patch('examples.write_on_file.file_writer.open', mock_open()) as mocked_file: 139 | FileWriter().write(fake_file_path, content) 140 | 141 | # assert if opened file on write mode 'w' 142 | mocked_file.assert_called_once_with(fake_file_path, 'w') 143 | 144 | # assert if write(content) was called from the file opened 145 | # in another words, assert if the specific content was written in file 146 | mocked_file().write.assert_called_once_with(content) 147 | 148 | ```` 149 | 150 | In this test, we mock the opening of the file with a context manager. 151 | The variable `mocked_file` is the mocked opened file. 152 | 153 | * `examples.write_on_file.file_writer.open` is the reference for where the object `open()` is being *looked up*. 154 | 155 | Inside the context manager, we can: 156 | 157 | * call our actual method `FileWriter().write(fake_file_path, content)` 158 | * assert if `mocked_file` was opened with the **specific file path: `fake_file_path`, and write open mode: `w`** 159 | * assert if `write()` from `mocked_file` was called with the parameter `content` 160 | 161 | See the [source code][write-on-file-source-code]. 162 | 163 | ## Congratulations 164 | 165 | You've just learned how to mock reading and writing on a file without even opening a real one. 166 | 167 | **That's a huge reason to celebrate. Congrats!!!** 168 | 169 | ![Let's celebrate](https://media.giphy.com/media/e4rNcOFD7YPlK/giphy.gif) 170 | 171 | ## What about now? 172 | * See other [examples][examples] 173 | * Go to [advanced mocking][advanced] 174 | * Go to [the essentials of mocking][essentials] 175 | * Go to [mocking main menu][mocking-main-menu] 176 | * Go to [summary][summary] 177 | 178 | ## Credits 179 | * [Official documentation][official-documentation] 180 | 181 | [official-documentation]: https://docs.python.org/3/library/unittest.mock.html 182 | [official-documentation-mock-open]: https://docs.python.org/3/library/unittest.mock.html#mock-open 183 | [official-documentation-where-to-patch]: https://docs.python.org/3/library/unittest.mock.html#where-to-patch 184 | [official-documentation-mock-object]: https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock 185 | 186 | [python-context-manager]: https://jeffknupp.com/blog/2016/03/07/python-with-context-managers/ 187 | 188 | [count-lines-source-code]: https://github.com/otrabalhador/python-testing-by-examples/tree/master/examples/count_lines_from_file 189 | [write-on-file-source-code]: https://github.com/otrabalhador/python-testing-by-examples/tree/master/examples/write_on_file 190 | 191 | [assert_called]: https://http.cat/204 192 | 193 | [reading-files]: #reading-files 194 | [writing-on-files]: #writing-on-files 195 | 196 | [wait]: https://media.giphy.com/media/xT9KVmZwJl7fnigeAg/giphy.gif 197 | 198 | [advanced]: ../advanced 199 | [examples]: ../examples 200 | [essentials]: ../essentials 201 | [mocking-main-menu]: ../ 202 | [summary]: ../../ 203 | --------------------------------------------------------------------------------