如何让pytest-django将数据提交到数据库而非仅存在事务中
核心原因
@pytest.mark.django_db标记仅对测试函数/测试类生效,你将该标记写在fixture定义上不会被pytest识别,你的测试仍然运行在默认的transaction=False模式下:pytest会为每个测试包裹一层自动回滚的事务,所有写入操作仅在该事务内可见,测试执行结束后直接回滚,不会真实提交到测试数据库的物理存储。- 你在pdb中能查到数据,是因为调试时fixture代码还在当前未回滚的事务上下文中,而GraphQL/REST请求会触发Django开启独立的请求数据库连接,无法读取未提交的事务数据,所以返回空结果。
- 你给fixture函数设置属性缓存
fake_user.user仅缓存了Python层的User对象实例,和数据库存储无关,事务回滚后该对象属于数据库游离态,不能代表数据库真实存储。
解决方案
方案1:开启事务提交模式(数据真实写入测试库)
将@pytest.mark.django_db(transaction=True)标记加在需要用到该fixture的测试函数/测试类上,不要加在fixture定义上:
# 测试函数写法 @pytest.mark.django_db(transaction=True) def test_my_api(fake_user, client): response = client.get("/api/users/") assert response.status_code == 200 # 此时请求能查到fake_user的数据
该模式下pytest会禁用自动回滚的包裹事务,所有数据库操作会真实提交,测试结束后pytest会执行flush清空表数据,不会保留到下一轮测试。
方案2:保留测试数据库便于调试
运行pytest时添加--reuse-db参数,pytest不会在测试结束后删除测试数据库,你可以直接连接到测试库(默认库名格式为test_<你的Django配置中DB_NAME>)查看写入的真实数据。
优化fixture写法
你不需要用函数属性缓存user实例,直接调整fixture作用域即可实现单例效果,修改后的fixture如下:
import pytest from django.utils import timezone from django.contrib.auth import get_user_model User = get_user_model() @pytest.fixture(scope="module") # 整个测试模块只创建一次 def fake_user(django_db_setup, django_db_blocker): with django_db_blocker.unblock(): user, _ = User.objects.get_or_create( id=1, defaults=dict( is_superuser=False, is_staff=False, email='foo@barmail.com', username='foobaruser', password='passwordmaster', date_joined=timezone.now() ) ) return user
内容的提问来源于stack exchange,提问作者Artem Bulatov
相关产品推荐
相关产品推荐

