Pytest-django: `mail.outbox` not being set?

Created on 10 Apr 2018  ·  14Comments  ·  Source: pytest-dev/pytest-django

Hey there, thanks for pytest-django! Appreciate all of the hard work, have used it quite a few times with no problems.

We're running into a bit of a snag where we upgraded some things and suddenly mail.outbox isn't retaining emails during tests. I've tried to figure out how exactly to use the mailoutbox fixture as documented here but I am having trouble using this.

Right now we're testing in a class and I have tried quite a few configurations to pass mailoutbox fixture. I.e. I updated the ini:

[pytest]
usefixtures = mailoutbox

Not exactly sure how to use that fixture though, I tried adding the positional argument mailoutbox to my class methods, still not able to access it.. I am sure I am missing something simple.

Would appreciate a push in the right direction -- and again, thanks so much for this project!

bug

Most helpful comment

I think I'm having the same issue, so I decided to strip down my test to a basic email sending and ran it individually:

def test_reset_password(mailoutbox, db, settings):
    text_message = render_to_string('emails/password_reset/password_reset_successful.txt',
                                    context={})
    html_message = render_to_string('emails/password_reset/password_reset_successful.html',
                                    context={})
    subject = render_to_string('emails/password_reset/password_reset_successful_subject.txt',
                               context={})

    email = EmailMultiAlternatives(
        subject=subject,
        body=text_message,
        from_email="[email protected]",
        to=['[email protected]']
    )

    if html_message:
        email.attach_alternative(html_message, "text/html")
    email.send()

    print(settings.EMAIL_BACKEND)

    assert len(mailoutbox) == 1
>       assert len(mailoutbox) == 1
E       assert 0 == 1
E        +  where 0 = len([])

accounts/tests/api/test_reset_password.py:200: AssertionError
----------------------------------------------- Captured stdout call ------------------------------------------------
django.core.mail.backends.locmem.EmailBackend
--------------------------------------------- Captured stdout teardown ----------------------------------------------

Now the cool part is if I remove the db fixture, the test passes. So I tried deleting my django_db_setup method, but to no avail...

I'm stuck at the moment.

All 14 comments

See https://github.com/pytest-dev/pytest-django/blob/5da0935731d71aa347c57cd1753f51e3ba9f32d5/docs/helpers.rst#clearing-of-mailoutbox (7aee367).
(Not clear if I understand the issue correctly though)

I think I'm having the same issue, so I decided to strip down my test to a basic email sending and ran it individually:

def test_reset_password(mailoutbox, db, settings):
    text_message = render_to_string('emails/password_reset/password_reset_successful.txt',
                                    context={})
    html_message = render_to_string('emails/password_reset/password_reset_successful.html',
                                    context={})
    subject = render_to_string('emails/password_reset/password_reset_successful_subject.txt',
                               context={})

    email = EmailMultiAlternatives(
        subject=subject,
        body=text_message,
        from_email="[email protected]",
        to=['[email protected]']
    )

    if html_message:
        email.attach_alternative(html_message, "text/html")
    email.send()

    print(settings.EMAIL_BACKEND)

    assert len(mailoutbox) == 1
>       assert len(mailoutbox) == 1
E       assert 0 == 1
E        +  where 0 = len([])

accounts/tests/api/test_reset_password.py:200: AssertionError
----------------------------------------------- Captured stdout call ------------------------------------------------
django.core.mail.backends.locmem.EmailBackend
--------------------------------------------- Captured stdout teardown ----------------------------------------------

Now the cool part is if I remove the db fixture, the test passes. So I tried deleting my django_db_setup method, but to no avail...

I'm stuck at the moment.

I forgot to mention there are some tests, in the same project, where the mailoutbox fixture seems to work, so I tried to spot any difference between those tests and the one that fails.
The only difference I found is the positioning of the mailoutbox argument in the test function. If it's before any fixture that depends on the db fixture it doesn't work, but it works if I put it after, so I guess I found a workaround.

I can confirm we are seeing this behaviour too with pytest-django==3.3.3

@bogdanpetrea
Thanks for the investigation / update.
Will be helpful for somebody debugging/fixing this.

Not sure if it is realy related, but I've found that there might be an AttributeError due to mail.outbox not being set in the first place - but this is related to changing the environment while pytest-django spins up.
Ref: https://github.com/pytest-dev/pytest-django/pull/708

I have currently the problem, that mailoutbox is not empty :(
It's the latest test function argument.

EDIT: I call mailoutbox.clear() before real test code as work-a-round.

@jedie
Can you provide a reproducible test case? A failing test for pytest-django would be the best, of course.

Related to #708? (i.e. poke around the code that it touches there)

I've created a trivial test case that reproduces the issue (at least the way I'm experiencing it): https://github.com/pytest-dev/pytest-django/compare/master...koniiiik:589-mailoutbox-is-not-django-core-mail-outbox?expand=1

From how I've used mailoutbox, it would also be very helpful to be able to have mailoutbox.clear() actually clear both mailoutbox and mail.outbox. If mailoutbox is mail.outbox, then someone could clear the outbox before running code that is then supposed to generate a specific number of emails (I.E. say 1 email).

Not sure if this is related to this issue, but the mailoutbox fixture does not match the behavior of mail.outbox in my test. It's not collecting sent emails:

(Pdb) mail.outbox
[<django.core.mail.message.EmailMultiAlternatives object at 0x7f6d864cb198>]
(Pdb) mailoutbox
[]
(Pdb)

Not sure if this is related to this issue, but the mailoutbox fixture does not match the behavior of mail.outbox in my test. It's not collecting sent emails:

(Pdb) mail.outbox
[<django.core.mail.message.EmailMultiAlternatives object at 0x7f6d864cb198>]
(Pdb) mailoutbox
[]
(Pdb)

Same.
mail.outbox contains a message while mailoutbox is empty.
pytest-django version is 3.9.0.

I have the same issue.

If I use the fixture like this is fails:

def test_send_foo_mail(mailoutbox, user_client, foo):

Like this it works:

def test_send_foo_mail(user_client, foo, mailoutbox):

It took me a lot of time to discover it. I would like to help to debug this. I looked at the implementation, but I don't have a clue how to debug this.

Related: https://stackoverflow.com/questions/66846621/mailoutbox-works-only-if-last-fixture

Experiencing the same behavior as previous commentator. Using it like this doesn't work:

def test_command_and_email(
    mailoutbox, argument, time_machine, mocker 
):

But everything works as exected if I just put it at the last place.

def test_command_and_email(
    argument, time_machine, mocker, mailoutbox
):

Argument argument is also a fixture that uses db fixture, so I guess that's the main reason of this behavior.
I'm using pytets-django 4.3.0

Was this page helpful?
0 / 5 - 0 ratings