ModuleNotFoundError when trying to use mock.patch on a method
Asked Answered
N

4

8

My pytest unit test keeps returning the error ModuleNotFoundError: No module name billing.

Oddly enough the send_invoices method in the billing module is able to be called when I remove the patch statement. Why is mock.patch unable to find the billing module and patch the method if this is the case?

billing.py

import pdfkit
from django.template.loader import render_to_string
from django.core.mail import EmailMessage
from projectxapp.models import User

Class Billing:

  #creates a pdf of invoice. Takes an invoice dictionary
  def create_invoice_pdf(self, invoice, user_id):
    #get the html of the invoice
    file_path ='/{}-{}.pdf'.format(user_id, invoice['billing_period'])
    invoice_html = render_to_string('templates/invoice.html', invoice)
    pdf = pdfkit.from_file(invoice_html, file_path)
    return file_path, pdf

  #sends invoice to customer
  def send_invoices(self, invoices):
    for user_id, invoice in invoices.items():
            #get customers email
            email = User.objects.get(id=user_id).email
            billing_period = invoice['billing_period']
            invoice_pdf = self.create_invoice_pdf(invoice, user_id)
            email = EmailMessage(
                'Statement of credit: {}-{}'.format(user_id, billing_period),
                'Attached is your statement of credit.\
                This may also be viewed on your dashboard when you login.',
                '[email protected]',
                [email],
            ).attach(invoice_pdf)

            email.send(fail_silently=False)

        return True

test.py

from mock import patch
from projectxapp import billing

@pytest.mark.django_db
def test_send_invoice():
  invoices = {
    1: {
        'transaction_processing_fee': 2.00,
        'service_fee': 10.00,
        'billing_period': '2020-01-02'
    },
    2: {
        'transaction_processing_fee': 4.00,
        'service_fee': 20.00,
        'billing_period': '2020-01-02'
    }
 }

  with patch('services.billing.Billing().create_invoice_pdf') as p1:
    p1.return_value = '/invoice.pdf'
    test = billing.Billing().send_invoices(invoices)

  assert test == True
Northeastward answered 5/4, 2020 at 14:25 Comment(0)
N
5

Solution

Since I had already imported the module of the method I needed to patch. I didn't need to use the full path including the package name.

Changed

patch('projectxapp.billing.Billing.create_invoice_pdf')

to this

patch('billing.Billing.create_invoice_pdf')

From the unittest documentation:

target should be a string in the form 'package.module.ClassName'. The target is imported and the specified object replaced with the new object, so the target must be importable from the environment you are calling patch() from. The target is imported when the decorated function is executed, not at decoration time.

Northeastward answered 27/4, 2020 at 12:6 Comment(0)
C
11

You should specify the full path to the module, including the package names, when you use patch. Also, do not use parentheses in the path. Modify the return_value attribute of the Mock object to customize the returning value of a call to the object:

with patch('projectxapp.billing.Billing.create_invoice_pdf') as p1:
    p1.return_value = '/invoice.pdf'
    test = billing.Billing().send_invoices(invoices)
Ceasefire answered 5/4, 2020 at 14:28 Comment(0)
N
5

Solution

Since I had already imported the module of the method I needed to patch. I didn't need to use the full path including the package name.

Changed

patch('projectxapp.billing.Billing.create_invoice_pdf')

to this

patch('billing.Billing.create_invoice_pdf')

From the unittest documentation:

target should be a string in the form 'package.module.ClassName'. The target is imported and the specified object replaced with the new object, so the target must be importable from the environment you are calling patch() from. The target is imported when the decorated function is executed, not at decoration time.

Northeastward answered 27/4, 2020 at 12:6 Comment(0)
I
1

Sometimes its just the name of the file that is wrong. Checkout the names of the files and ensure that they are same as required by the decorater.

Ilion answered 17/6, 2022 at 12:3 Comment(0)
C
-1

Since, this is the first google hit for ModuleNotFoundError with patch...

In my case, it was an issue with the letter case (com.foo.SftpClient instead of com.foo.SFTPClient)

In my env (python 3.6), the interpreter complained that com.foo module could not be found even though the actual reason was a typo in the class name

Complex answered 23/8, 2021 at 13:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.