Beliaf
Beliaf

Reputation: 615

How to correctly use assertRaises in Django

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?

Upvotes: 12

Views: 15744

Answers (4)

andyhasit
andyhasit

Reputation: 15289

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

Upvotes: 0

Andrew Veitch
Andrew Veitch

Reputation: 56

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.

Upvotes: 3

solarissmoke
solarissmoke

Reputation: 31404

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

Upvotes: 31

Thibault Deutsch
Thibault Deutsch

Reputation: 136

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

Upvotes: 8

Related Questions