Vidya
Vidya

Reputation: 657

Tests that verify the calling of remove function of the Python os module fail

I am trying to mock Python os module but my mocking steps are not working.

The code in the file os_mock.py:

import os 

class MyTestMock:

    def rm(self):
        # some reason file is always hardcoded
        file_path = "/tmp/file1"
        if os.path.exists(file_path):
            os.remove(file_path)
            print(file_path, 'removed successfully')
        else:
            print(file_path, 'Does not exist')

The code in the test case file test_os_mock.py

import os
import unittest
from unittest.mock import patch
from os_mock import MyTestMock

class TestMyTestMock(unittest.TestCase):
    @patch('os.path')
    @patch('os.remove')
    def test_rm(self, mock_remove, mock_path):
        my_test_mock = MyTestMock()
        mock_path.exists.return_vallue = False
        my_test_mock.rm()
        self.assertFalse(mock_remove.called)

        mock_path.exists.return_vallue = True
        my_test_mock.rm()
        self.assertTrue(mock_remove.called)

I am getting below error when I execute test cases

F
======================================================================
FAIL: test_rm (__main__.TestMyTestMock)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/unittest/mock.py", line 1336, in patched
    return func(*newargs, **newkeywargs)
  File "/Users/vuser/code/MP-KT/mock/test_os_mock.py", line 15, in test_rm
    self.assertFalse(mock_remove.called)
AssertionError: True is not false

----------------------------------------------------------------------
Ran 1 test in 0.008s

FAILED (failures=1)

I know I am doing something wrong while mocking, But I could not able to figure it out, I got couple of stack overflow links, I followed it, but no help.

Upvotes: 0

Views: 398

Answers (1)

User051209
User051209

Reputation: 2548

I have made some change only to your test file, while the file os_mock.py remains unchanged.

Typo: return_vallue --> return_value

If you change return_vallue to return_value your test passes successfully so these changes (in the 2 points where the errors are present) are sufficient.

In particular the changes are the followings:

  • mock_path.exists.return_vallue=False --> mock_path.exists.return_value=False (return_vallue is not correct)
  • mock_path.exists.return_vallue=True --> mock_path.exists.return_value=True (return_vallue is not correct)

Remove the import os

I have removed the import os from the file test_os_mock.py because the patch() function operates on the script os_mock.py and it is not necessary in the test file.
The test works with the import too but it is less clear!

Use of assert_not_called and assert_called_once

In my opinion the package unittest.mock provides methods assert_not_called() and assert_called_once() who can improve your tests.
For example self.assertTrue(mock_remove.called) ensures you called the mocked method, instead mock_remove.assert_called_once() checks that you called the method exactly one time.

So I recommend the followings changes:

  • self.assertFalse(mock_remove.called) --> mock_remove.assert_not_called()
  • self.assertTrue(mock_remove.called) --> mock_remove.assert_called_once()

The new file test_os_mock.py

With the changes showed, the file test_os_mock.py becomes:

#import os    # <--- I remove this import because patch operates on the file os_mock
import unittest
from unittest.mock import patch
from os_mock import MyTestMock

class TestMyTestMock(unittest.TestCase):

    @patch('os.path')
    @patch('os.remove')
    def test_rm(self, mock_remove, mock_path):
        my_test_mock = MyTestMock()

        # return_vallue --> return_value
        mock_path.exists.return_value = False
        my_test_mock.rm()
        mock_remove.assert_not_called()
        #self.assertFalse(mock_remove.called)

        # return_vallue --> return_value
        mock_path.exists.return_value = True
        my_test_mock.rm()
        mock_remove.assert_called_once()
        #self.assertTrue(mock_remove.called)

if __name__ == "__main__":
    unittest.main()

Use of assert_called_once_with

In your test case better than assert_called_once() is the method assert_called_once_with() which verifies the value of the argument passed to the method. With this other method your test becomes as follow:

@patch('os.path')
@patch('os.remove')
def test_rm(self, mock_remove, mock_path):
    my_test_mock = MyTestMock()
    mock_path.exists.return_value = False
    my_test_mock.rm()
    mock_remove.assert_not_called()

    mock_path.exists.return_value = True
    my_test_mock.rm()
    # here is the NEW CHANGE
    mock_remove.assert_called_once_with("/tmp/file1") 

This link is very useful for understanding Mocking object in Python. It explains also the use of mock.patch() function.

Upvotes: 3

Related Questions