Función simulada open () utilizada en un método de clase

Intenté burlarme de la función abierta utilizada en un método de mi clase. Encontré este hilo ¿Cómo me burlo de un abierto usado en una statement with (usando el framework Mock en Python)? pero no pude resolver mi problema. Además, la documentación de unittest muestra una solución que tampoco se burlaba de mi https://docs.python.org/3/library/unittest.mock-examples.html#patch-decorators

Esta es mi clase con el método donde se usa la función abierta:

#__init.py__ import json class MyClass: def save_data_to_file(self, data): with open('/tmp/data.json', 'w') as file: json.dump(data, file) ... mc = MyClass() 

Ahora encontré una solución un poco diferente. Esta es mi prueba:

 #save_to_file_test.py from mymodule import MyClass from mock import mock_open, patch import ast class SaveToFileTest(unittest.TestCase): def setUp(self): self.mc = MyClass() self.data = [ {'id': 5414470, 'name': 'peter'}, {'id': 5414472, 'name': 'tom'}, {'id': 5414232, 'name': 'pit'}, ] def test_save_data_to_file(self): m = mock_open() with patch('mymodule.open', m, create=True): self.mc.save_data_to_file(self.data) string = '' for call in m.return_value.write.mock_calls: string += (call[1][0]) list = ast.literal_eval(string) assertEquals = (list, self.data) 

No estoy seguro de si esta es la mejor manera de probar el contenido que se debe escribir en un archivo. Cuando pruebo los mock_calls (call_args_list es el mismo) estos son los argumentos que se pasan al identificador de archivo. Cualquier consejo, mejoras y sugerencias son bienvenidos.

TL; DR

El corazón de su problema es que también debería estar burlándose de json.dump para poder probar correctamente los datos que se escribirán en su archivo. De hecho, tuve dificultades para ejecutar su código hasta que se hicieron algunos ajustes importantes a su método de prueba.

  • Simulacro con builtins.open y no mymmodule.open
  • Usted está en un administrador de contexto, por lo que debería estar marcando m.return_value.__enter__.write , sin embargo, en realidad está llamando a la escritura desde json.dump que es donde se llamará la escritura. (Detalles a continuación sobre una solución sugerida)
  • También debe json.dump para validarlo simplemente se llama con sus datos

En resumen, con los problemas mencionados anteriormente, el método puede reescribirse como:

Detalles sobre todo esto abajo

 def test_save_data_to_file(self): with patch('builtins.open', new_callable=mock_open()) as m: with patch('json.dump') as m_json: self.mc.save_data_to_file(self.data) # simple assertion that your open was called m.assert_called_with('/tmp/data.json', 'w') # assert that you called m_json with your data m_json.assert_called_with(self.data, m.return_value) 

Explicación detallada

Para centrarme en los problemas que veo en su código, lo primero que sugiero encarecidamente, ya que open es una función incorporada, es burlarse de las funciones integradas, además, puede ahorrarse una línea de código haciendo uso de new_callable y as , as simplemente puedes hacer esto

 with patch('builtins.open', new_callable=mock_open()) as m: 

El siguiente problema que veo con su código es que tuve problemas para ejecutar esto hasta que realicé el siguiente ajuste cuando comenzó a hacer un bucle en sus llamadas:

 m.return_value.__enter__.return_value.write.mock_calls 

Para analizar eso, lo que debe tener en cuenta es que su método está utilizando un administrador de contexto. Al utilizar un administrador de contexto, el trabajo de su escritura se realizará realmente dentro de su método __enter__ . Por lo tanto, a partir del valor de return_value de su m , desea obtener el valor de __enter__ de __enter__ .

Sin embargo, esto nos lleva al corazón del problema con lo que está intentando probar. Debido a cómo funciona el json.dump al escribir en el archivo, sus mock_calls para su escritura después de inspeccionar el código, se verán así:

  call('[') call('{') call('"name"') call(': ') call('"peter"') call(', ') call('"id"') call(': ') call('5414470') call('}') call(', ') call('{') call('"name"') call(': ') call('"tom"') call(', ') call('"id"') call(': ') call('5414472') call('}') call(', ') call('{') call('"name"') call(': ') call('"pit"') call(', ') call('"id"') call(': ') call('5414232') call('}') call(']') call.__str__() 

Eso no va a ser divertido de probar. Entonces, esto nos lleva a la siguiente solución que puedes probar; Simulacro de json.dump .

No deberías probar json.dump, deberías probar llamándolo con los parámetros correctos. Dicho esto, puedes seguir una moda similar con tu burla y hacer algo como esto:

 with patch('json.dump') as m_json: 

Ahora, con eso, puede simplificar significativamente su código de prueba, simplemente para validar que se llame al método con sus datos con los que está probando. Entonces, con eso, cuando lo pongan todo junto, tendrán algo como esto:

 def test_save_data_to_file(self): with patch('builtins.open', new_callable=mock_open()) as m: with patch('json.dump') as m_json: self.mc.save_data_to_file(self.data) # simple assertion that your open was called m.assert_called_with('/tmp/data.json', 'w') # assert that you called m_json with your data m_json.assert_called_with(self.data, m.return_value.__enter__.return_value) 

Si está interesado en la refactorización adicional para que su método de prueba sea un poco más limpio, también puede configurar su parche como decorador, dejando su código más limpio dentro del método:

 @patch('json.dump') @patch('builtins.open', new_callable=mock_open()) def test_save_data_to_file(self, m, m_json): self.mc.save_data_to_file(self.data) # simple assertion that your open was called m.assert_called_with('/tmp/data.json', 'w') # assert that you called m_json with your data m_json.assert_called_with(self.data, m.return_value.__enter__.return_value) 

La inspección es su mejor amigo aquí, para ver qué métodos se están llamando en qué pasos, para ayudar más con las pruebas. Buena suerte.