84

I have a model called Thing with an attribute called name, and I want name to be a char field that's only 3 characters long.

How do I write a test for that?

class TestCase1(TestCase):
    def test1(self):
        thing = Thing(name='1234')

that test should fail. How do I properly write the test so that the test passes when that object fails?

Alexander Bird
  • 38,679
  • 42
  • 124
  • 159

4 Answers4

159

If you're expecting Thing(name='1234') to raise an exception, there are two ways to deal with this.

One is to use Django's assertRaises (actually from unittest/unittest2):

def mytest(self):
    self.assertRaises(FooException, Thing, name='1234')

This fails unless Thing(name='1234') raises a FooException error. Another way is to catch the expected exception and raise one if it doesn't happen, like this:

def mytest(self):
    try:
        thing = Thing(name='1234')
        self.fail("your message here")
    except FooException:
        pass

Obviously, replace the FooException with the one you expect to get from creating the object with too long a string. ValidationError?

A third option (as of Python 2.7) is to use assertRaises as a context manager, which makes for cleaner, more readable code:

def mytest(self):
    with self.assertRaises(FooException):
        thing = Thing(name='1234')

Sadly, this doesn't allow for custom test failure messages, so document your tests well. See https://hg.python.org/cpython/file/2.7/Lib/unittest/case.py#l97 for more details.

GDorn
  • 8,511
  • 6
  • 38
  • 37
  • I use assertRaises quite often. – Matthew Schinckel Nov 19 '10 at 10:37
  • I do as well, but the syntax takes some getting used to. – GDorn Nov 19 '10 at 17:44
  • 1
    in python version 2.6.5, the function signature is different. Copied from python code: `def failUnlessRaises(self, excClass, callableObj, *args, **kwargs)` and then `callableObj(*args, **kwargs)`. So in this post's example, it would be `self.assertRaises(FooException, Thing, name='1234')`. I'm not sure why my function signature is different, but that's what works. – Alexander Bird Feb 05 '11 at 21:20
  • Actually, I think it's been that way for some time. I just got the sig backwards. I'll update my answer. – GDorn Feb 07 '11 at 19:25
  • 2
    `assertRaises` can also be used in a with statement like this: `with self.assertRaises(FooException): thing = Thing(name='1234')` – antonagestam Jul 09 '15 at 10:55
  • That is to say, assertRaises is also a context manager. See the example use case here: http://stackoverflow.com/a/8215739/402605 – GDorn Jul 16 '15 at 18:22
  • In case you run into a `TransactionManagementError`, see https://stackoverflow.com/a/23326971 – djvg Mar 08 '21 at 10:59
7

I am currently using the expectedFailure decorator from unittest. That works as advertised: Fails when there is no error, passes when there is a failure.

I use expectedFailure to verify that my custom assert-routines actually work and not just rubberstamp everything OK.

import unittest
from django.test import TestCase

class EmojiTestCase(TestCase):

    @unittest.expectedFailure
    def testCustomAssert(self):
        self.assertHappyFace(':(') # must fail.

But prints a warning-message during testing. I use it with Django and Nose. Which others have seen, too.

/usr/lib64/python3.4/unittest/case.py:525: RuntimeWarning: TestResult has no addExpectedFailure method, reporting as passes RuntimeWarning)

I came here to find a better solution, but found none. So I at least wanted to tell others that come what I've been working with.

Chris
  • 5,788
  • 4
  • 29
  • 40
7

In my previous project i had to do something like test driven development, so i have written some test case which must catch certain types of error. If it don't gets the error then i have messed up something. Here i share my code.

from django.test import TestCase
from django.contrib.auth.models import User

class ModelTest(TestCase):

def test_create_user_with_email(self):

    with self.assertRaises(TypeError):
        email = "ah@gmail.com"
        password = 'testpass1'

        user = User.objects.create_user(
            email = email,
            password = password,)

        self.assertEqual(user.email, email)
        self.assertTrue(user.check_password(password))

You can see i have tried to create a user with email and password but default Django user model need "username" and "password" arguments to create user. So here my testcase must raise "TypeError". And that what i tried to do here.

-6

Something like this should work:

thing = Thing.objects.create(name='1234')  
# not sure if you need here .get() or if. create() truncates the field on its own
self.assertEqual(thing.name, '123') # or: self.assertEqual(len(thing.name), 3)

-- but such test looks weird :-)

Also, note that MySQLdb backend will raise Warning exception to notify you of truncating the string, so you might want to check it with assertRaises.

Tomasz Zieliński
  • 16,136
  • 7
  • 59
  • 83