Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to test Models in Django with Foreign Keys

I want to make sure I am testing Models/Objects in isolation and not as one huge system.

If I have an Order object and it has Foreign Keys to Customers, Payments, OrderItems, etc. and I want to test Order functionality, I need to create fixtures for all of that related data, or create it in code. I think what I really need to be doing is mocking out the other items, but I don't see an easy (or possible) solution for that if I am doing queries on these Foreign Keys.

The common solutions (fixtures) don't really let me test one Object at a time. I am sure this is partly caused by my app being way over coupled.

I am trying my darndest to adopt TDD as my main method of working, but the way things work with Django, it seems you can either run very trivial unit tests, or these massive integration tests.

[Edit] Better explicit example and a some more humility

What I mean is that I seem to be able to only run trivial unit tests. I have seen people with very well tested and granular modules. I am certain some of this can be followed back to poor design.

Example:

I have a model call Upsell which is linked to a Product model. Then I have a Choices model which are children of Upsell (do you want what's behind door #1, #2, #3).

The Upsell model has several methods on it that derive items necessary to render the template from their choices. The most important one is that it creates a URL for each choice. It does this through some string mangling etc. If I wanted to test the Upsell.get_urls() method, I want to have it not depend on the values of choices in the fixtures, and I want to not have it depend on the value of Product in the fixtures.

Right now I populate the db in the setUp method for the tests, and that works well with the way Django backs out the transaction every time, but only outside of setUp and tearDown. This works fairly well except some of the Models are fairly complex to set up, while I actually only need to get one attribute for it.

I can't give you an example of that, since I can't accomplish it, but here is the type of thing I am doing now. Basically I input an entire order, create the A/B experiment it was attached to, etc. And that's not counting Product, Categories, etc. all set up by fixtures. It's not the extra work I am concerned as I can't even test one database-based object at a time. The Tests below are important, but they are integration tests. I would like to be building up to something like this by testing each item separately. As you pointed out, maybe I shouldn't have chosen a framework so closely tied to the db. Does any sort of dependency injection exist with something like this? (beyond my testing, but the code itself as well)

class TestMultiSinglePaySwap(TestCase):
    fixtures = ['/srv/asm/fixtures/alchemysites.json','/srv/asm/fixtures/catalog.json','/srv/asm/fixtures/checkout_smallset.json','/srv/asm/fixtures/order-test-fixture.json','/srv/asm/fixtures/offers.json']

def setUp(self):
    self.o = Order()
    self.sp = SiteProfile.objects.get(pk=1)
    self.c = Customer.objects.get(pk=1)
    signals.post_save.disconnect(order_email_first, sender=Order)
    self.o.customer = self.c
    p = Payment()
    p.cc_number = '4444000011110000'
    p.cc_exp_month = '12'
    p.cc_type = 'V'
    p.cc_exp_year = '2020'
    p.cvv2 = '123'
    p.save()
    self.o.payment = p
    self.o.site_profile = self.sp
    self.o.save()
    self.initial_items = []
    self.main_kit = Product.objects.get(pk='MOA1000D6')
    self.initial_items.append(self.main_kit)
    self.o.add_item('MOA1000D6', 1, False)
    self.item1 = Product.objects.get(pk='MOA1041A-6')
    self.initial_items.append(self.item1)
    self.o.add_item('MOA1041A-6', 1, False)
    self.item2 = Product.objects.get(pk='MOA1015-6B')
    self.initial_items.append(self.item2)
    self.o.add_item('MOA1015-6B', 1, False)
    self.item3 = Product.objects.get(pk='STP1001-6E')
    self.initial_items.append(self.item3)
    self.o.add_item('STP1001-6E', 1, False)
    self.swap_item1 = Product.objects.get(pk='MOA1041A-1')

def test_single_pay_swap_wholeorder(self):
    o = self.o
    swap_all_skus(o)
    post_swap_order = Order.objects.get(pk = o.id)
    swapped_skus = ['MOA1000D','MOA1041A-1','MOA1015-1B','STP1001-1E']
    order_items = post_swap_order.get_all_line_items()
    self.assertEqual(order_items.count(), 4)
    pr1 = Product()
    pr1.sku = 'MOA1000D'
    item = OrderItem.objects.get(order = o, sku = 'MOA1000D') 
    self.assertTrue(item.sku.sku == 'MOA1000D')
    pr2 = Product()
    pr2.sku = 'MOA1015-1B'
    item = OrderItem.objects.get(order = o, sku = 'MOA1015-1B') 
    self.assertTrue(item.sku.sku == 'MOA1015-1B')
    pr1 = Product()
    pr1.sku = 'MOA1041A-1'
    item = OrderItem.objects.get(order = o, sku = 'MOA1041A-1') 
    self.assertTrue(item.sku.sku == 'MOA1041A-1')
    pr1 = Product()
    pr1.sku = 'STP1001-1E'
    item = OrderItem.objects.get(order = o, sku = 'STP1001-1E') 
    self.assertTrue(item.sku.sku == 'STP1001-1E')

Note that I have never actually used a Mock framework though I have tried. So I may also just be fundamentally missing something here.

like image 530
zenWeasel Avatar asked Sep 11 '10 06:09

zenWeasel


People also ask

How does foreign key work in Django?

Introduction to Django Foreign Key. A foreign key is a process through which the fields of one table can be used in another table flexibly. So, two different tables can be easily linked by means of the foreign key. This linking of the two tables can be easily achieved by means of foreign key processes.

Why we use foreign key in Django models?

ForeignKey is a Django ORM field-to-column mapping for creating and working with relationships between tables in relational databases. ForeignKey is defined within the django.

Can a model have two foreign keys Django?

Your intermediate model must contain one - and only one - foreign key to the source model (this would be Group in our example), or you must explicitly specify the foreign keys Django should use for the relationship using ManyToManyField.

How does Django store foreign key values?

Note that the _id in the artist parameter, Django stores foreign keys id in a field formed by field_name plus _id so you can pass the foreign key id directly to that field without having to go to the database again to get the artist object.


2 Answers

Look into model mommy. It can automagically create objects with Foreign Keys.

like image 75
MikeTwo Avatar answered Oct 08 '22 14:10

MikeTwo


This will probably not answer your question but it may give you food for thought.

In my opinion when you are testing a database backed project or application there is a limit to what you can mock. This is especially so when you are using a framework and an ORM such as the one Django offers. In Django there is no distinction between the business model class and the persistence model class. If you want such a distinction then you'll have to add it yourself.

Unless you are willing to add that additional layer of complexity yourself it becomes tricky to test the business objects alone without having to add fixtures etc. If you must do so you will have to tackle some of the auto magic vodoo done by Django.

If you do choose to grit your teeth and dig in then Michael Foord's Python Mock library will come in quite handy.

I am trying my darndest to adopt TDD as my main method of working, but the way things work with Django, it seems you can either run very trivial unit tests, or these massive integration tests.

I have used Django unit testing mechanism to write non-trivial unit tests. My requirements were doubtless very different from yours. If you can provide more specific details about what you are trying to accomplish then users here would be able to suggest other alternatives.

like image 24
Manoj Govindan Avatar answered Oct 08 '22 13:10

Manoj Govindan