python - 单元测试序列化程序 Django Rest Framework
问题描述
我是 django 的新手,我想测试我的 CRUD 以获得我所做的 Approval 类,但我遇到了错误:
回溯(最后一次调用):文件“E:\Django_Workspace\berryenroll\BerryEnroll\api\tests.py”,第 19 行,在 test_add serializer.create(validated_data) 文件“E:\Django_Workspace\berryenroll\BerryEnroll\api\ serializers.py",第 29 行,在 create valid_data['username'] = self.context['request'].user.username KeyError: 'request'
这是测试类:
class ApprovalTest(TestCase):
def test_add(self):
validated_data = dict()
validated_data['status'] = 'APPROVED'
validated_data['given_role'] = ['ROLE_ADMIN']
validated_data['asked_role'] = ['ROLE_ADMIN']
serializer = ApprovalRequestSerializer(data=validated_data)
if serializer.is_valid():
print(serializer.data)
serializer.create(validated_data)
else:
print(serializer.errors)
这是带有 customUser 的 Approval 模型:
matrix_roles = settings.MATRIX_RULES
allowed_roles = list(set([elem.role for elem in matrix_roles])) # Convert to a unique list
LEXERS = [item for item in get_all_lexers() if item[1]]
LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS])
STYLE_CHOICES = sorted([(item, item) for item in get_all_styles()])
STATUS_CHOICES = sorted([(item, item) for item in ["PENDING", "APPROVED", "REJECTED", "CANCELED"]])
ROLE_CHOICES = sorted([(item, item) for item in allowed_roles])
class ApprovalRequest(models.Model):
# TODO: Add caller_id ?
username = models.CharField(blank=True, default='', max_length=255)
status = models.CharField(choices=STATUS_CHOICES, default='PENDING', max_length=100)
creation_date = models.DateTimeField(auto_now_add=True)
created_by = models.CharField(max_length=255, blank=True, default='username')
last_update = models.DateTimeField(null=True)
updated_by = models.CharField(max_length=255, blank=True, null=True)
asked_role = MultiSelectField(choices=ROLE_CHOICES, default=settings.ASKED_DEFAULT_ROLE, max_length=100)
given_role = MultiSelectField(choices=ROLE_CHOICES, default=settings.ASKED_DEFAULT_ROLE, max_length=100)
# given_role = models.CharField(choices=ROLE_CHOICES, default=settings.ASKED_DEFAULT_ROLE, max_length=100)
class Meta:
ordering = ['id']
class CustomUser(AbstractBaseUser, PermissionsMixin):
username = models.CharField(_('username'), unique=True, max_length=255)
roles = []
objects = None
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = []
class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')
def save (self, **kwargs):
"""saving to DB disabled"""
pass
def get_group_permissions (self):
"""If you don't make your own permissions module,
the default also will use the DB. Throw it away"""
return [] # likewise with the other permission defs
def get_and_delete_messages (self):
"""Messages are stored in the DB. Darn!"""
return []
这是 ApprovalSerializer:
class ApprovalRequestSerializer(serializers.ModelSerializer):
given_role = fields.MultipleChoiceField(choices=ROLE_CHOICES,required=False)
asked_role = fields.MultipleChoiceField(choices=ROLE_CHOICES,required=False)
class Meta:
fields = '__all__'
model = ApprovalRequest
read_only_fields = ['username', 'creation_date', 'created_by', 'last_update', 'updated_by']
def create(self, validated_data):
now = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
"""Override default dataform read-only fields"""
validated_data['username'] = self.context['request'].user.username
validated_data['status'] = 'PENDING'
validated_data['created_by'] = self.context['request'].user.username
validated_data['creation_date'] = now
validated_data['updated_by'] = self.context['request'].user.username
validated_data['last_update'] = now
return super(ApprovalRequestSerializer, self).create(validated_data)
def update(self, instance, validated_data):
now = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
validated_data['updated_by'] = self.context['request'].user.username
validated_data['last_update'] = now
res = super(ApprovalRequestSerializer, self).update(instance, validated_data)
# Check if the new role is approved, the call the Gateway to update the user role
if 'status' in validated_data.keys():
if validated_data['status'] == 'APPROVED':
logger.info("The role should be updated to %s" % list(validated_data['given_role']))
server_url = settings.BCS_URL
username = settings.BCS_ADMIN_USERNAME
password = settings.BCS_ADMIN_PASSWORD
bcs = System(server_url)
bcs.login_as_admin(username, password)
users = bcs.users()
# Look for my user in the gateway and update it's 'authorities'
for user in users:
if user['login'] == instance.username:
user['authorities'] = list(validated_data['given_role'])
bcs.update_user(user)
# TODO: Log warning if the user does not exist / This case should never happen / Synchro error with gateway
# pass
return res
我只想测试 create 和 update 方法,但我不知道如何添加 context['request'] 来测试它
解决方案
您需要使用 DRF 的 RequestFactory,如下所示:
from rest_framework.test import APIRequestFactory
class ApprovalTest(TestCase):
def test_add(self):
# ...
user = SomeUserFactory()
request_factory = APIRequestFactory()
request = request_factory.post('/')
request.force_authenticate(user)
serializer = ApprovalRequestSerializer(data=validated_data, context={"request": request})
请注意,在某些情况下,您传递给请求工厂的 URL 可能很重要。此外,您可能需要覆盖测试用例中使用的 URL - 我的意思是有时您有应用程序使用的主 urls.py 和与 API 对应的其他一些 urls.py(当您有两个为应用程序和 API 服务的不同工作人员)。您可以通过urls
像这样设置测试用例的 classvar 来做到这一点:
from django.test import override_settings
@override_settings(ROOT_URLCONF='my_app.urls')
class ApprovalTest(TestCase):
def test_add(self):
# ....
但是,由于您的序列化程序依赖于请求和用户,您也可以考虑为视图本身编写测试。然后视图将在后台调用序列化程序,因此您将能够通过视图测试序列化程序,如下所示:
from rest_framework.test import APIClient
class MyViewTestCase(TestCase):
def test_add(self):
user = SomeUserFactory()
client = APIClient()
# you can also use force_authenticate here to indicate that the user has
# already been logged in or actually test what happens when you don't do this
# presumabely the view should return a 401 or something similar.
client.force_authenticate(user)
# url could either be a path, e.g. /some-url/
# url = "/some-api-endpoint/"
# or you could use reverse, like so:
# url = reverse('my-view-name', kwargs={ ... })
response = client.post(url, data={"given_role": "something", "asked_role": "something-else"})
# now you can check either the response code, like so:
self.assertEqual(response.status_code, 200)
# or you can check the content returned like so:
self.assertEqual(response.json(), {"some": "expected", "json": "output"})
我希望这对你有所帮助。我也强烈建议你看看 pytest 和 pytest-django。感谢 pytest 夹具支持,您可以非常快速地摆脱代码复制。然后,您也可以用语句替换self.assert*()
调用,assert foo == bar
尽管这当然是个人偏好,与您的问题的本质无关。
推荐阅读
- python - CGI 和内容类型:text/html
- java - 增加多维数组的维度
- bluetooth-lowenergy - 什么是 BLE 标签以及 BLE 标签、信标和 iOS 定制应用程序之间的关系
- ruby-on-rails - 基于列的平均值进行过滤的 Rails 范围
- html - 如何垂直对齐伪元素?
- jmeter - 使用 Jmeter 验证保存在数据库中的 CSV 文件内容
- java - 无法使用非对称加密解密 Spring Cloud 配置中的配置属性
- c# - 如何将对象构建器注入 MVC 控制器?
- eclipse - 使用 JSF 显示列表
- tomcat - 在 Tomcat 上切换 HTTP 和 HTTPS 时出现 Cookie 问题