Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mock pyplot.show in python (to prevent showing plots)

I want to assert that matplotlib.pyplot.show was called in one of my class's methods, but I don't want the method to actually call show() and plot on an external window during the unit test. No matter how I try to mock matplotlib, it doesn't seem to work. I'll list the important parts of my code below:

#In Model Test Class:
import Model
import matplotlib
import unittest
from unittest.mock import patch

class Model_Test(unittest.TestCase):

    @patch('matplotlib.pyplot')
    def test_plot_data(self, mock_pyplot):
        model = Model()
        model.plot_data()
        mock_pyplot.show.assert_called_once()

#In Model Class:
import matplotlib.pyplot as plt
import pandas as pd

class Model(object):
    ... # (__init__ and other methods)
    def plot_data(self):
        self.dataframe.plot(y=self.dataframe.columns, subplots=False, figsize=(15, 8), fontsize=12)
        plt.title('Data for Model Version: ' + self.version, fontsize=20)
        plt.xlabel('timestamp', fontsize=12)
        plt.ylabel('value', fontsize=12)
        plt.show()

I also tried setting a return value for mock_plot.show. When I use the debugger, matplotlib within the Model class is not propagated with a Mock object, so I am unsure how to mock the matplotlib module or its member functions. Is it possible to mock matplotlib?

Any ideas for how to properly mock matplotlib in unit tests (mainly to prevent displaying plots)?

EDIT: I was able to get it to work by changing the patch decoration to @patch('matplotlib.pyplot.show'), but I'm still unsure why this worked and my previous attempt didn't.

like image 298
glaceonix Avatar asked Jul 19 '18 18:07

glaceonix


People also ask

How do you not show a plot in Python?

If we are in interactive mode, the plot might get displayed. To avoid the display of plot we use close() and ioff() methods.

What is PLT show block false?

plt.show(block=False) #this creates an empty frozen window. _ = raw_input("Press [enter] to continue.") if __name__ == '__main__': main()


1 Answers

When you call patch, the matplotlib module is patched in-memory so any new attempts to access it's child element pyplot will reference the mock. However, the module that houses Model already has a direct reference to matplotlib.pyplot; it obtained that reference when you imported it. It won't go through the patched matplotlib module again. When you patch show directly, patch will instead modify the shared pyplot module.

We can illustrate this by also obtaining a direct reference to the original show function on module load:

# bla.py
#import numpy as np
import matplotlib.pyplot as plt

show = plt.show

class A:
    def __init__(self):
        print(type(plt))
        print(type(plt.show))
        print(type(show))

Python console:

>>> from unittest.mock import patch
>>> from bla import A
>>> with patch('matplotlib.pyplot') as p:
...   A()
... 
<class 'module'>
<class 'function'>
<class 'function'>
<bla.A object at 0x7f232b98a400>
>>> with patch('matplotlib.pyplot.show') as p:
...   A()
... 
<class 'module'>
<class 'unittest.mock.MagicMock'>
<class 'function'>
<bla.A object at 0x7f232b98d550>

You'll note that the reference to show that we obtained in the bla module remains un-mocked.

like image 57
Thom Wiggers Avatar answered Nov 15 '22 04:11

Thom Wiggers