nteract / testbook

🧪 📗 Unit test your Jupyter Notebooks the right way
https://testbook.readthedocs.io
BSD 3-Clause "New" or "Revised" License
416 stars 37 forks source link

Support for objects as return_value when patching #133

Open laserprec opened 2 years ago

laserprec commented 2 years ago

Is it possible to patch an object and set its return_value to another object?

For example let's say a Jupyter Notebook defines a method that downloads some CSV from the internet and load it in as a pandas.DataFrame.

import pandas as pd
import requests

def load_csv_from_internet():
    csv_file = requests.get(....) # fetch some CSV from the internet
    ...
    return pd.read_csv(csv_file) # read it and return as pandas.DataFrame

When writing a unit test for such method, I would like to avoid the making the external call and probably patch the return_value of load_csv_from_internet() to some fake pandas.DataFrame I pre-computed. For example:

#setup a fake pandas.DataFrame
mock_df = pd.DataFrame(np.random.randint(0,100,size=(100, 4)))
# patching the `load_csv_from_internet` method from the Notebook
with tb.patch('__main__.load_csv_from_internet', return_value=mock_df) as mock_data:
    .... # some assertion check

Unfortunately, at the moment when assigning the return_value to an object, the object is casted to string type leading to the following error:

from unittest.mock import patch
E                           _patcher_yrvzguhxnm = patch(
E                               "__main__.load_csv_from_internet",
E                               **{"return_value": "     0   1   2   3
E               0    2  66  60  22
E               1   87   1  65  54
E               2   95  99   7  20
E               3   45  84  48  66
E               4   85  96  85  23
E               ..  ..  ..  ..  ..
E               95  79  66  35  94
E               96  91  50  43  87
E               97   5  84  14  18
E               98  13  30  23  71
E               99  66  74  83  66
E               
E               [100 rows x 4 columns]"}
E                           )
E                           _mock_iqufzomgiw = _patcher_yrvzguhxnm.start()
E               
E               ------------------
E               
E                 File "<ipython-input-4-6c61a9b5d34f>", line 4
E                   **{"return_value": "     0   1   2   3
E                                                         ^
E               SyntaxError: EOL while scanning string literal
E               
E               SyntaxError: EOL while scanning string literal (<ipython-input-4-6c61a9b5d34f>, line 4)

Is there plan to support returning objects as it is?

MSeal commented 2 years ago

Complex objects that don't have a serialization, like dataframes and mocks, need to be explicitly injected. We need to brainstorm some easier ways to represent complex object mock pass down, perhaps testing and enabling mock object side-effects.

But ultimately to make this work you'll need to tb.inject the code that's needed to do the mocking:

tb.inject('''
mock_df = pd.DataFrame(np.random.randint(0,100,size=(100, 4)))
unittest.mock.patch('__main__.load_csv_from_internet', return_value=mock_df)
''')

See https://github.com/nteract/testbook/issues/72 for some further notes on the topic.