1

I'm trying to use mock_open() to test my application Mocking file reads using patch decorator and side_effect, but I'm unable to check a global value set from the function under test. It only works when there is a value returned from the function.

My application code snippet looks something like

#!/usr/bin/env python
 # -*- coding: utf-8 -*-

thresh_dict = {}
allowed_san_dict = {}
max_eval_count = ""
clear_eval_count = ""

def generate_cfg_thresh_map():
    """ For generating a mapping table containing the thresholds for each of the
    production devices. The file is read one line at a time and split on whitespace
    to get the individual fields
    """

    global allowed_san_dict
    global max_eval_count
    global clear_eval_count
    try:
        with open ('/opt/config/pwr_thresh_cfg', 'r') as fh:
            for line in fh:
                split_line = line.split()
                if line.startswith("ENTRY_THRESH_COUNT"):
                    max_eval_count = int(split_line[1])
                elif line.startswith("EXIT_THRESH_COUNT"):
                    clear_eval_count = int(split_line[1])
                else:
                    thresh_dict[split_line[0]]  = [int(split_line[1]), int(split_line[2]), int(split_line[3])]
                    allowed_san_dict[split_line[0]] = int(split_line[4])

    except Exception as error:
        log_to_journal('read failed from pwr_thresh_cfg')

and the test_app.py file looks like

#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Include require py.test libraries for the suite to run
import unittest
import pytest
import mock

import app

from mock import patch, mock_open

class ReportMonitor(unittest.TestCase):
    def test_generate_cfg_thresh_map(self):
        with patch('app.open', mock_open(read_data='ENTRY_THRESH_COUNT 200')) as _:
            app.generate_cfg_thresh_map()
            assert app.max_eval_count == 200
            print('value for %s' % (app.max_eval_count))
            print('value for %s' % (app.generate_cfg_thresh_map()))

The problem is I want to assert on the value of max_eval_count set in the generate_cfg_thresh_map() function. Since the value is set in a global variable and not returned from the function, I can't assert the value in my test case. I cannot use return from the function i.e. not change the application code at all.

How would I do this? i.e. run the function under test and check the value set by the function for the test string and assert the unit test case depending on the value set by the function.

Inian
  • 80,270
  • 14
  • 142
  • 161
  • The issue is not with the global variables, they will be tested just fine with the existing code. `read_data` doesn't mock the file object's iterator, so either change to `for line in fh.readlines()` in the function, or monkeypatch the `mock_open`'s mock iteration machinery. – hoefling Jun 13 '19 at 14:43
  • Example for monkeypatching iteration in `mock_open`: https://stackoverflow.com/q/24779893/2650249 – hoefling Jun 13 '19 at 14:54

1 Answers1

1

Citing the mock_open docs:

read_data is a string for the read(), readline(), and readlines() methods of the file handle to return. Calls to those methods will take data from read_data until it is depleted.

This means that read_data will not be invoked when the mocked file object is iterated. An easy fix is to change the implementation of the generate_cfg_thresh_map function to:

def generate_cfg_thresh_map():
    ...
    try:
        with open ('/opt/config/pwr_thresh_cfg', 'r') as fh:
            for line in fh.readlines():
                ...

If you can't change the function, alternatively you can enhance the mock object returned by mock_open:

Python 3

class ReportMonitor(unittest.TestCase):
    def test_generate_cfg_thresh_map(self):
        m = mock_open(read_data='ENTRY_THRESH_COUNT 200')
        m.return_value.__iter__ = lambda self: self
        m.return_value.__next__ = lambda self: next(iter(self.readline, ''))
        with patch('app.open', m):
            app.generate_cfg_thresh_map()
            assert app.max_eval_count == 200

Python 2

class ReportMonitor(unittest.TestCase):
    def test_generate_cfg_thresh_map(self):
        m = mock_open(read_data='ENTRY_THRESH_COUNT 200')
        m.return_value.__iter__ = lambda self: iter(self.readline, '')
        with patch('app.open', m):
            app.generate_cfg_thresh_map()
            assert app.max_eval_count == 200

Credits: Customizing unittest.mock.mock_open for iteration.

hoefling
  • 59,418
  • 12
  • 147
  • 194