pytest-django icon indicating copy to clipboard operation
pytest-django copied to clipboard

`mail.outbox` not being set?

Open ckcollab opened this issue 6 years ago • 14 comments

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!

ckcollab avatar Apr 10 '18 18:04 ckcollab

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)

blueyed avatar Apr 14 '18 08:04 blueyed

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.

bogdanpetrea avatar Jun 19 '18 11:06 bogdanpetrea

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.

bogdanpetrea avatar Jun 20 '18 09:06 bogdanpetrea

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

oppianmatt avatar Jul 31 '18 12:07 oppianmatt

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

blueyed avatar Jul 31 '18 17:07 blueyed

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

blueyed avatar Mar 09 '19 23:03 blueyed

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 avatar Apr 01 '19 13:04 jedie

@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)

blueyed avatar Apr 01 '19 16:04 blueyed

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

koniiiik avatar Aug 30 '19 13:08 koniiiik

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).

MicahLyle avatar Jan 17 '20 21:01 MicahLyle

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)

jshields avatar May 13 '20 17:05 jshields

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.

sanjioh avatar Jun 19 '20 15:06 sanjioh

I have the same issue.

If I use the fixture like this it 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

guettli avatar Mar 29 '21 06:03 guettli

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

FerenetsRoman avatar Jun 18 '21 09:06 FerenetsRoman