How to correctly use assertRaises in Django
Asked Answered
H

4

12

I have the following validation function in my model:

@classmethod
def validate_kind(cls, kind):
    if kind == 'test':
        raise ValidationError("Invalid question kind")

which I am trying to test as follows:

w = Model.objects.get(id=1) 

self.assertRaises(ValidationError, w.validate_kind('test'),msg='Invalid question kind')

I also tried:

self.assertRaisesRegex(w.validate_kind('test'),'Invalid question kind')

Both of these don't work correctly. What am I doing wrong?

Hysterectomize answered 21/4, 2018 at 10:43 Comment(0)
S
31

The way you are calling assertRaises is wrong - you need to pass a callable instead of calling the function itself, and pass any arguments to the function as arguments to assertRaises. Change it to:

self.assertRaises(ValidationError, w.validate_kind, 'test')
Shaer answered 21/4, 2018 at 11:11 Comment(0)
I
8

The accepted answer isn't correct. self.assertRaises() doesn't check the exception message.

Correct answer

If you want to assert the exception message, you should use self.assertRaisesRegex().

self.assertRaisesRegex(ValidationError, 'Invalid question kind', w.validate_kind, 'test')

or

with self.assertRaisesRegex(ValidationError, 'Invalid question kind'):
    w.validate_kind('test')
Intellectualize answered 15/3, 2021 at 22:40 Comment(0)
A
3

I would do:

with self.assertRaises(ValidationError, msg='Invalid question kind'):
    w.validate_kind('test')

This may well be a change in Python since the question was originally asked.

Aliform answered 3/7, 2023 at 12:0 Comment(0)
I
0

Do not use assertRaises to check the message, because it doesn't do what you think it does.

Use assertRaisesMessage instead:

with self.assertRaisesMessage(ValidationError, "Invalid question kind"):
   w.validate_kind('test')

This will assert that "Invalid question kind" is in the error message, which is usually all you want to test (if you want more control, use assertRaisesRegex).

Why assertRaises is dangerous:

First you need to be aware there are two ways you can use assertRaises (documented here)

1 - By passing the callable itself:

self.assertRaises(ValidationError, w.validate_kind, 'test')

The args/kwargs after the callable are passed to the callable, so there's no way to check the message.

2 - By using as a context:

with self.assertRaises(ValidationError, msg='Invalid question kind'):
    w.validate_kind('test')

Here msg is simply set on the captured exception, so you can use it later. It does not check the message in the error raised by your function, and so the test will pass even if the messages don't match.

Try it and see:

def foo():
    raise ValidationError('abc')

def test_foo():
    with self.assertRaises(ValidationError, msg='xyz') as e:
        foo()
    print(e.exception, e.msg)

The test will pass and you'll see the following print out:

abc xyz
.
-------------------------------
Ran 1 test in 0.002s

That's one of the reasons why in TDD they always advise you to write a test that fails first, and then make it pass.

For more info, read Django's testing docs

Inulin answered 10/8, 2024 at 12:20 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.