Django pytest测试中用户Profile的identity_number无法获取问题排查
问题背景
我实现了一个Django重置密码功能:当用户已认证时,会进入修改密码流程,此时从用户的Profile模型中获取identity_number作为nationalId。但用pytest编写测试用例时,明明创建了测试用户并设置了Profile的identity_number,测试中该字段却为空,导致接口返回WrongInputs错误。
相关代码如下:
views.py
@method_decorator(csrf_protect, name='dispatch') class ResetPassword(APIView): permission_classes = (permissions.AllowAny, ) def post(self, request): try: # 读取输入 nid = request.data.get('nationalId') # 根据输入判断流程阶段... if not request.user.is_anonymous and request.user.is_authenticated: user = request.user nid = user.profile.identity_number # 后续逻辑... except Exception as e: # 错误处理... return Response({'State': 'Error', 'Message': 'WrongInputs', 'Data': {}})
tests.py
import pytest from django.contrib.auth.models import User from myapp.models import Profile from rest_framework.test import APIClient from rest_framework import status @pytest.fixture def test_user_nid(): user = User.objects.create_user( username="8835246563", password="upass123", ) # 尝试更新Profile,但未生效 profile = Profile.objects.update(user=user, identity_number="8835246563") return user @pytest.mark.django_db class TestResetPassword: url = '/api/reset-password/' # 假设接口URL def test_change_password_stages(self, api_client, test_user_nid): api_client.force_authenticate(user=test_user_nid) print("User ID:", test_user_nid.id) print("User Profile Identity Number:", test_user_nid.profile.identity_number) data = { "tfaCode": 12345 } response = api_client.post(self.url, data, format='json') response_data = response.json() print(">>>>>", response_data) # 再次验证Profile字段 profile = test_user_nid.profile print("Profile Identity Number:", profile.identity_number)
models.py
import uuid from django.db import models from django.contrib.auth.models import User class Profile(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) user = models.OneToOneField(User, on_delete=models.CASCADE) identity_number = models.CharField(max_length=20, blank=True, default='')
测试打印结果
User ID: 12 User Profile Identity Number: >>>>> {'State': 'Error', 'Message': 'WrongInputs', 'Data': {}} Profile Identity Number:
问题原因排查
Profile更新方式错误:
测试fixture中使用Profile.objects.update(user=user, identity_number="8835246563")是错误的。update()是批量更新操作,此写法会尝试将所有Profile实例的user字段设置为当前测试用户,并更新identity_number,而非定位到当前用户对应的Profile进行修改。如果项目通过post_save信号自动为新User生成Profile,这条语句会修改所有已有Profile;如果没有自动生成逻辑,该语句不会创建新Profile,后续访问user.profile时Django会自动生成一个空Profile,导致identity_number为空。ORM缓存问题:
即使正确更新了Profile,Django ORM会缓存已加载的对象实例。若更新后未重新获取用户或刷新Profile实例,test_user_nid.profile会保留缓存中的旧数据,导致读取到的identity_number为空。
解决方案
方法一:修复测试fixture的Profile创建/更新逻辑
根据项目是否自动生成Profile,选择对应方式:
情况1:无自动创建Profile的信号
直接创建Profile实例并关联用户:
@pytest.fixture def test_user_nid(): user = User.objects.create_user( username="8835246563", password="upass123", ) # 直接创建关联的Profile Profile.objects.create(user=user, identity_number="8835246563") # 刷新用户对象,确保关联的Profile被加载 user.refresh_from_db() return user
情况2:有自动创建Profile的信号
通过filter定位目标Profile再更新,或直接修改实例保存:
@pytest.fixture def test_user_nid(): user = User.objects.create_user( username="8835246563", password="upass123", ) # 方式1:筛选目标Profile并更新 Profile.objects.filter(user=user).update(identity_number="8835246563") # 方式2:直接修改实例并保存(更直观) # profile = user.profile # profile.identity_number = "8835246563" # profile.save() # 刷新用户对象的Profile缓存 user.refresh_from_db() return user
方法二:其他创建测试用户的可行方式
1. 使用get_or_create确保Profile存在
适用于不确定是否有自动创建逻辑的场景:
@pytest.fixture def test_user_nid(): user = User.objects.create_user( username="8835246563", password="upass123", ) # 确保Profile存在并设置identity_number profile, created = Profile.objects.get_or_create(user=user) profile.identity_number = "8835246563" profile.save() user.refresh_from_db() return user
2. 使用Factory Boy工厂函数
适合测试用例较多的场景,简化用户和Profile的创建:
首先安装依赖:
pip install factory-boy
创建工厂类:
# tests/factories.py import factory from django.contrib.auth.models import User from myapp.models import Profile class UserFactory(factory.django.DjangoModelFactory): class Meta: model = User username = factory.Sequence(lambda n: f"user{n}") password = factory.PostGenerationMethodCall('set_password', 'testpass123') class ProfileFactory(factory.django.DjangoModelFactory): class Meta: model = Profile user = factory.SubFactory(UserFactory) identity_number = factory.Sequence(lambda n: f"id{n}")
测试中使用:
@pytest.fixture def test_user_nid(): user = UserFactory(username="8835246563") ProfileFactory(user=user, identity_number="8835246563") user.refresh_from_db() return user
3. 手动创建并关联(基础方式)
@pytest.fixture def test_user_nid(): user = User.objects.create( username="8835246563", is_active=True, ) user.set_password("upass123") user.save() profile = Profile.objects.create( user=user, identity_number="8835246563" ) return user
验证修改效果
修改fixture后重新运行测试,test_user_nid.profile.identity_number应能正确输出设置的值,接口也不会再返回WrongInputs错误。
内容的提问来源于stack exchange,提问作者HesamHashemi

