Unittest: How can you set attributes in a class - unit-testing

When creating a unit test, I cannot seem to set attributes in one test, to be used in another test. The two tests in question must happen in sequential order because the attribute set in one test is directly used in another test.
To give an example, let's say I have a class called Foo, which has two methods, one called test_set_bar which sets an attribute bar, and another called test_get_bar which gets it:
Whenever I try to run the two 'tests' however, I get the following error:
AttributeError: 'Test_Foo' object has no attribute 'bar'
Thus, I cannot seem to set attributes in the normal way.
I know that I can do this in the setUp, however, the thing that would need to go in setUp itself is actually the return value of a function, which itself is being tested. Thus, it must be set during the execution of a test.

You can make use of setup method for that purpose.
from unittest import TestCase
class TestFoo(TestCase):
def setUp(self):
self.bar = 1
def test_bar(self):
assert self.bar, 1

Related

Django unit testing: Separating unit tests without querying the database multiple times

I have a pair of tests like this:
Make sure a task's deletion status initializes as None
def test_initial_task_deletion_status_is_none(self):
unfinished_task = Task.objects.get(tasked_with="Unfinished Test")
self.assertIsNone(unfinished_task.delete_status)
# Make sure a task's deletion status changes appropriately
def test_unfinished_task_deletion_status_updates_appropriately(self):
unfinished_task = Task.objects.get(tasked_with="Unfinished Test")
unfinished_task.timed_delete(delta=.1)
self.assertIs(unfinished_task.delete_status, "Marked for Deletion")
This will go on, but I'll have unfinished_task = Task.objects.get(tasked_with="Unfinished Test") at the beginning of every one. Is there a way to split these types of things into separate tests, but use the same query result?
Assuming you're using Django's testing framework, then you can do this using setUp().
More about unittest.TestCase.setUp() here
So your updated snippet would look like:
from django.test import TestCase
class MyTestCase(TestCase):
def setUp(self):
self.unfinished_task = Task.objects.get(tasked_with="Unfinished Test")
def test_initial_task_deletion_status_is_none(self):
self.assertIsNone(self.unfinished_task.delete_status)
# Make sure a task's deletion status changes appropriately
def test_unfinished_task_deletion_status_updates_appropriately(self):
self.unfinished_task.timed_delete(delta=.1)
self.assertIs(self.unfinished_task.delete_status, "Marked for Deletion")
You can place the repeated line in the setUp method, and that will make your code less repetitive, but as DanielRoseman pointed out, it will still be run for each test, so you won't be using the same query result.
You can place it in the setUpTestData method, and it will be run only once, before all the tests in MyTestCase, but then your unfinished_task object will be a class variable, shared across all the tests. In-memory modifications made to the object during one test will carry over into subsequent tests, and that is not what you want.
In read-only tests, using setUpTestData is a good way to cut out unnecessary queries, but if you're going to be modifying the objects, you'll want to start fresh each time.

Django DRF APITestCase chain test cases

For example I want to write several tests cases like this
class Test(APITestCase):
def setUp(self):
....some payloads
def test_create_user(self):
....create the object using payload from setUp
def test_update_user(self):
....update the object created in above test case
In the example above, the test_update_user failed because let's say cannot find the user object. Therefore, for that test case to work, I have to create the user instead test_update_user again.
One possible solution, I found is to run create user in setUp. However, I would like to know if there is a way to chain test cases to run one after another without deleting the object created from previous test case.
Rest framework tests include helper classes that extend Django's existing test framework and improve support for making API requests.
Therefore all tests for DRF calls are executed with Django's built in test framework.
An important principle of unit-testing is that each test should be independent of all others. If in your case the code in test_create_user must come before test_update_user, then you could combine both into one test:
def test_create_and_update_user(self):
....create and update user
Tests in Django are executed in a parallell manner to minimize the time it takes to run all tests.
As you said above if you want to share code between tests one has to set it up in the setUp method
def setUp(self):
pass

Using a custom manager function in a model definition

I'm trying to use manager function to populate a default field of a Model, but I'm having trouble getting it working. Here's my code:
class FooManager(models.Manager):
def foo_filter(self):
return Foo.objects.latest('id')
class Foo(models.Model):
objects = FooManager()
name = models.CharField(
max_length=100,
unique=True,
default=objects.foo_filter())
Whenever I run it,
I get NameError: global name 'Foo' is not defined on the line return Foo.objects.latest('id')
Ha! This is a cool one :)
When your models.py gets loaded for the first time, Python is trying to parse all the class definitions.
So the first thing it sees is FooManager, so far so good.
In your filter method you make use of Foo, which is still unknown at this point. Usually that would not be a problem, because you are using Foo in a method and that method only gets called during runtime, when all the classes are already loaded.
Now Python sees the Foo class and on the name field you are calling one of the manager's methods. At this point, when your filter method is called, the Foo class has not been fully loaded, yet, so when you enter the filter method, Foo is indeed not defined.
You would have to move the manager definition below the Foo definition, but you can't do that, because then the line objects = FooManager() would fail because FooManager would be undefined.
By the way: This is a very common newbie error: You must never put method calls into your field definitions. It means that the method is only executed once when the server starts and Django loads all the model definitions. This leads to extremely frustrating errors:
At first, the default value of your field is indeed the latest Foo object, but when you add a new object to the database, it will not change to the new latest object, it will stay the same object that was the latest when the server was started.
Django usually allows you to put methods into your field definitions, so you could write:
default=objects.foo_filter
without the (). Now, when the server is loaded, the method will not be executed. It will only be executed, when Django actually needs to retrieve the default value, and at that point in time, both, FooManager and Foo have already been loaded.
But apart from that, your manager doesn't even need to know which model it belongs to. Have a look at the Django docs here: https://docs.djangoproject.com/en/1.7/topics/db/managers/#modifying-initial-manager-querysets
Each manager has a get_queryset method which you can override and which you should use, so you could do the following:
class FooManager(models.Manager):
def foo_filter(self):
return self.get_queryset().latest('id')
Now your manager doesn't use the Foo class at all and you won't get into any circular dependency problems.
By the way, I don't think that latest('id') will work, you have to pass in a field name that is a date field, not an ID field.
You are create a loop, that object already is Foo.objects. All you need is:
def foo_filter(self):
return self.latest('id')
On top of that you can't use the database to set defaults for a field like that.

django changes in tests.py don't reflect in models.py

I have a recurring problem when testing my app. whenever I change or create() any object from within tests.py, these changes can't be found in models.py - and that happens in same test.
pseudocode:
tests.py:
def test_something(self):
...
Norm.objects.create(...)
self.player_a.print_all_norms()
...
models.py:
def print_all_norms():
a = Norm.objects.all()
print a
# prints [], the Norm object created in tests.py wasn't found
return
EDIT:
Clarification - I can't find the object within the test that created it.
A Norm object is created inside test_something(), which calls a function inside models.py.
When the function tries to find the previously created object using Norm.objects.all(), it fails, the test resumes, and then test fails as well.
Testing uses temporary database as documented in the test database docs, so after the test is complete, you won't be able to find those objects through the model manager.
Is it not finding the object within the test or when you try to find it after executing the test?
If it's not finding it in the test, try making sure you have the proper permissions (as mentioned in test db docs)
If you want to load predetermined values into the database on some sort of consistent basis, outside of testing, you may want to at using fixtures

copy.copy(object) returns an object with id == None during tests?

I am new to Django and Python, building my first app using TDD... I wanted to copy an instance of Task, a model object. I used the following code, which works correctly during the tests:
import copy
class Task(models.Model):
...
def make_copy(self):
new_task = copy.copy(self)
new_task.save()
return new_task
But when running this code 'normally', in the server, I noticed it was not working: no new object was created. I found out I had to add new_task.id = None just before saving, and I understand the reason for this...
But if copy.copy does not know about Django and thus will not change the id itself, why is it the case that the object returned has id == None during the tests?
It sounds like your test case is not fully matching the usage in your "normal" use case.
The id field is set for objects that exist in the database. If you pass your make_copy() method an object with id set, it will appear to fail because it's not making a new database object, it's just saving an existing object (through the Python copy, with an existing id).
I'd guess your test case is passing Task objects to make_copy() with id of None (and therefore appearing to work), while in "normal" usage, the objects are coming in with id set. (You could test this hypothesis with a simple print or assert statement).
One simple solution might be to set id to None right after your copy operation. That way, a new database object is always created.
Finally, someone else with the same situation: http://www.nerdydork.com/copy-model-object-in-django.html