Tamdim
Tamdim

Reputation: 1192

ModuleNotFoundError when trying to use mock.patch on a method

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

Upvotes: 8

Views: 21142

Answers (4)

Kaushal Banthia
Kaushal Banthia

Reputation: 164

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.

Upvotes: 1

qwerty
qwerty

Reputation: 3869

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

Upvotes: -1

Tamdim
Tamdim

Reputation: 1192

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.

Upvotes: 5

blhsing
blhsing

Reputation: 106553

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)

Upvotes: 11

Related Questions