Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python unit test mock. ValueError: The truth value of a DataFrame is ambiguous

I am writing a unit test case for one of my python 2.7 methods.

In my test method, there is a method call that takes a dictionary with string key and panadas dataframe as the value for that key.

I want to write an interaction test for this method to check if it calls the method internally with correct dictionary

def MethodUnderTest(self):
    #some code here
    externalMethod(dictionary_of_string_dataframe)
    #some code here

In the unit test, I do write my assert to test this interaction like this

mock_externalClass.externalMethod.assert_called_once_with(dictionary_of_string_dataframe) 

I create dictionary_of_string_dataframe exactly the same way it is created in the actual method. In fact, I copied the helper method that does that in the test code just to make sure that both the dictionaries are the same. I even print both the dictionaries while debugging the test method on python console and both look exactly the same.

And I patch the external class using @patch decorator and all that works fine.

The problem is that in the above mentioned assert statement, i get the following error:

 mock_externalClass.externalMethod.assert_called_once_with(dictionary_of_string_dataframe)
  File "C:\Python27\lib\site-packages\mock\mock.py", line 948, in assert_called_once_with
    return self.assert_called_with(*args, **kwargs)
  File "C:\Python27\lib\site-packages\mock\mock.py", line 935, in assert_called_with
    if expected != actual:
  File "C:\Python27\lib\site-packages\mock\mock.py", line 2200, in __ne__
    return not self.__eq__(other)
  File "C:\Python27\lib\site-packages\mock\mock.py", line 2196, in __eq__
    return (other_args, other_kwargs) == (self_args, self_kwargs)
  File "C:\Python27\lib\site-packages\pandas\core\generic.py", line 953, in __nonzero__
    .format(self.__class__.__name__))
ValueError: The truth value of a DataFrame is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().

I did search on the valueError but not much of help. Can someone tell me what's going on here ?

I did check the following question but that didn't help

ValueError: The truth value of a DataFrame is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all()

like image 778
Hary Avatar asked Jun 19 '17 22:06

Hary


3 Answers

I also ran into this issue while trying to test that a function I wrote is called with a certain preprocessed dataframe, and I solved it using mock's call_args attribute along with pandas's testing.assert_frame_equal.

In my case I wanted to assert the value of the dataframe passed as the second argument to my function below called score_function that is called by the higher-level function run_scoring. So first I retrieved the *args part of the method call to the mock using [0] and then I got my second positional argument (which is the one I wanted to assert the value of) using [1].

Then I could assert the value of this dataframe using pd.testing.assert_frame_equal.

from unittest.mock import Mock
import pandas as pd

import my_code

...

score_function_mocked = Mock()
my_code.score_function = score_function_mocked
my_code.run_scoring()

pd.testing.assert_frame_equal(
    # [0] = *args, [0][1] = second positional arg to my function
    score_function_mocked.call_args[0][1],
    pd.DataFrame({
        'new_york': [1, 0],
        'chicago': [0, 1],
        'austin': [0, 0]
    })

)
like image 99
sfogle Avatar answered Oct 13 '22 15:10

sfogle


This happens because unittest.mock compares between input values using == or !=. However, pandas dataframes cannot be similarly compared, and instead, you must use the .equals method of DataFrames.

https://github.com/testing-cabal/mock/blob/master/mock/mock.py

One possible fix is to write your own unit test that iterates through the dictionary and compares between dataframes using the .equals method.

Another is to override the __equals__ method of pandas dataframes so when mock compares between them it will use the correct method.

like image 28
victor Avatar answered Oct 13 '22 14:10

victor


Here is how I would solve it based on the original question. This borrows some of the thinking in sfogle's answer, but it allows you to test called functions generally whether the args are DataFrames or not.

import unittest
from unittest.mock import patch
import pandas as pd

def external_method(df):
    return df

def function_to_test(df):
    external_method(df)
    return df

class MyTest(unittest.TestCase):

    def test_the_function_to_test(self):
        my_test_df = pd.DataFrame({"a": [1, 2, 3, 4]})    
        with patch(__name__ + ".external_method") as mock_external_method:        
            function_to_test(my_test_df)
            mock_external_method.assert_called_once()
            args, kwargs = mock_external_method.call_args
            self.assertTrue(args[0].equals(my_test_df))
like image 28
Scott Morken Avatar answered Oct 13 '22 15:10

Scott Morken