You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为全外键字段的Django模型编写DRF测试用例?

问题描述

我有一个所有字段均为其他模型外键的Example模型,代码如下:

class Example(models.Model):
    package = models.ForeignKey(
        Destination, related_name="packages", on_delete=models.CASCADE
    )
    user = models.ForeignKey(
        User,
        on_delete=models.CASCADE,
        null=True,
        related_name="user_packages",
    )
    tour = models.ForeignKey(
        Tours,
        on_delete=models.CASCADE,
        null=True,
        related_name="tour_packages",
    )

我已知单外键模型的测试用例编写方式(示例如下):

class NewsLetter(models.Model):
    NewsLetterID = models.AutoField(primary_key=True)
    Email = models.CharField(max_length=255)
    Connected = models.BooleanField(default=False)
    UserID = models.ForeignKey(User, on_delete=models.CASCADE)
    class Meta:
        db_table = 'NewsLetter'

@classmethod
def setUpTestData(cls):
    #Set up non-modified objects used by all test methods
    user = User.objects.create(<fill params here>)
    NewsLetter.objects.create(NewsLetterID=1, Email='test@test.com', Connected=False,UserID=user)

但我的Example模型中,关联模型(如Destination)本身也包含外键字段,请问如何为该模型的创建API编写Django REST Framework单元测试?


解决方案

核心思路是从最底层的依赖模型开始创建实例,逐层往上构建所有关联对象,再测试API的创建逻辑。以下是具体实现步骤:

1. 构建所有依赖模型实例

假设Destination模型依赖于另一个Country模型(可根据你的实际依赖替换),首先需要在测试的setUpTestData中创建最底层的依赖,再依次创建Destination、User、Tours,最后才能生成API测试所需的请求数据或直接创建Example实例。

示例依赖模型(替换为你的实际模型结构即可):

class Country(models.Model):
    name = models.CharField(max_length=100)

class Destination(models.Model):
    name = models.CharField(max_length=100)
    country = models.ForeignKey(Country, on_delete=models.CASCADE)

class Tours(models.Model):
    name = models.CharField(max_length=100)

2. 编写DRF单元测试用例

使用Django的TestCase和DRF的APIClient来测试创建API,具体代码如下:

from django.test import TestCase
from django.urls import reverse
from rest_framework.test import APIClient
from rest_framework import status
from django.contrib.auth.models import User
from .models import Country, Destination, Tours, Example

class ExampleAPITestCase(TestCase):
    @classmethod
    def setUpTestData(cls):
        # 创建最底层依赖:Country
        cls.country = Country.objects.create(name="Test Country")
        # 创建Destination(依赖Country)
        cls.destination = Destination.objects.create(name="Test Destination", country=cls.country)
        # 创建User
        cls.user = User.objects.create_user(username="testuser", password="testpass123")
        # 创建Tours
        cls.tour = Tours.objects.create(name="Test Tour")
        
        # 初始化API客户端
        cls.client = APIClient()
        # 登录客户端(如果API需要认证)
        cls.client.login(username="testuser", password="testpass123")

    def test_create_example_api(self):
        # 构造POST请求的数据,外键字段传入对应模型的主键ID
        url = reverse("example-list")  # 替换为你的Example API的列表/创建路由名称
        data = {
            "package": self.destination.id,
            "user": self.user.id,
            "tour": self.tour.id
        }

        # 发送POST请求
        response = self.client.post(url, data, format="json")

        # 断言请求成功
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        
        # 断言数据库中创建了对应的Example记录
        self.assertEqual(Example.objects.count(), 1)
        created_example = Example.objects.first()
        self.assertEqual(created_example.package, self.destination)
        self.assertEqual(created_example.user, self.user)
        self.assertEqual(created_example.tour, self.tour)

    def test_create_example_with_null_fields(self):
        # 测试允许为null的外键字段不传值的情况
        url = reverse("example-list")
        data = {
            "package": self.destination.id
            # 不传user和tour,因为它们允许为null
        }

        response = self.client.post(url, data, format="json")
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        
        created_example = Example.objects.first()
        self.assertIsNone(created_example.user)
        self.assertIsNone(created_example.tour)

关键注意事项

  • 依赖顺序:必须先创建被外键引用的模型实例,比如先创建Country才能创建Destination,否则会抛出数据库外键约束错误。
  • API路由:确保reverse("example-list")中的名称与你的urls.py中定义的API路由名称一致。
  • 认证:如果你的API需要登录才能创建,记得在setUpTestData中登录客户端;如果不需要认证,可以去掉登录代码。
  • 字段验证:如果序列化器有额外的验证逻辑,需要补充对应测试用例,比如测试传入不存在的外键ID时是否返回400错误。

内容的提问来源于stack exchange,提问作者Reactoo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 08:15:40