Django APITestCase中的模型冲突

时间:2019-04-13 09:28:03

标签: python django django-rest-framework

我正在尝试为DRF API设置测试。我有2个型号:

class Project(models.Model):
    name = models.CharField(max_length=300, unique=True)
    description = models.CharField(
        max_length=2000,
        blank=True,
        null=True,
        default=None
    )
    created_at = models.DateTimeField(auto_now_add=True)

class TemporaryUser(models.Model):
    username = models.CharField(max_length=400)
    hash_id = models.URLField(default=_generate_unique_hash, unique=True)
    project = models.ForeignKey(
        Project,
        on_delete=models.CASCADE,
        related_name='users'
    )

我决定为每个测试文件单独设置setUp方法,所以我的2个测试文件如下所示:

test_projects.py

class ProjectViewsTest(APITestCase):
client = APIClient()

@classmethod
def setUpClass(cls):
    project = Project.objects.create(name="Test Project")
    cls.project_creation_date = datetime.now().strftime(
        '%Y-%m-%d %H:%M:%S'
    )
    Project.objects.create(
        name="Test Project #2", description='Testing Project Number 2'
    )

    session = QuestionSession.objects.create(project=project)
    cls.session_creation_date = datetime.now().strftime(
        '%Y-%m-%d %H:%M:%S'
    )

    Question.objects.create(
        description='Test Question #1',
        question_type='TEXT',
        answers_to_close=50,
        question_session=session
    )
    Question.objects.create(
        description='Test Question #2',
        question_type='TEXT',
        answers_to_close=50,
        question_session=session
    )
    Question.objects.create(
        description='Test Question #3',
        question_type='TEXT',
        answers_to_close=50,
        question_session=session
    )

@classmethod
def tearDownClass(cls):
    Project.objects.all().delete()

def test_projects_fetched_with_sessions_number(self):
    """Test multiple projects are fetched with sessions counter"""

    self.maxDiff = None

    response = self.client.get(
        'http://testserver/api/projects/', format='json'
    )

    self.assertEqual(response.status_code, 200)
    self.assertJSONEqual(
        response.content,
        {
            "projects": [
                {
                    "id": 1,
                    "name": "Test Project",
                    "description": None,
                    "sessions_number": 1,
                    "created_at": self.project_creation_date
                },
                {
                    "id": 2,
                    "name": "Test Project #2",
                    "description": "Testing Project Number 2",
                    "sessions_number": 0,
                    "created_at": self.project_creation_date
                }
            ]
        }
    )

def test_project_fetched_with_minified_sessions(self):
    """Test a project is fetched with minified sessions"""

    response = self.client.get(
        'http://testserver/api/projects/1/',
        format='json'
    )

    self.assertEqual(response.status_code, 200)
    self.assertJSONEqual(
        response.content,
        {
            "id": 1,
            "name": "Test Project",
            "created_at": self.project_creation_date,
            "sessions": [
                {
                    "id": 1,
                    "name": None,
                    "active": True,
                    "created_at": self.session_creation_date,
                    "priority": 0,
                    "questions_number": 3
                }
            ]
        }
    )

def test_project_creation(self):
    """Test project creation with/without passing description"""

    # POST with description
    response_1 = self.client.post(
        'http://testserver/api/projects/create/',
        format='json',
        data={"name": "Test Project #3", "description": "Another Project"}
    )

    # POST without description
    response_2 = self.client.post(
        'http://testserver/api/projects/create/',
        format='json',
        data={"name": "Test Project #4"}
    )

    self.assertEqual(response_1.status_code, 200)
    self.assertEqual(response_2.status_code, 200)

    self.assertJSONEqual(
        response_1.content,
        {
            'status': 'SUCCESS', 'id': 3,
            'message': 'Test Project #3 has been created successfully!'
        }
    )

    self.assertJSONEqual(
        response_2.content,
        {
            'status': 'SUCCESS', 'id': 4,
            'message': 'Test Project #4 has been created successfully!'
        }
    )

    project_1 = Project.objects.get(id=3)
    project_2 = Project.objects.get(id=4)

    self.assertEqual(project_1.description, "Another Project")
    self.assertEqual(project_2.description, "")

def test_project_deletion(self):
    """Test API call removes Project from database"""

    self.assertTrue(Project.objects.filter(id=1).exists())

    response = self.client.delete(
        'http://testserver/api/projects/1/delete/',
        format='json'
    )

    self.assertEqual(response.status_code, 200)
    self.assertJSONEqual(
        response.content,
        {
            'status': 'SUCCESS',
            'message': 'The project #1 has been deleted'
        }
    )

    self.assertFalse(Project.objects.filter(id=1).exists())

test_users.py

class TemporaryUserViewsTest(APITestCase):
client = APIClient()

@classmethod
def setUpClass(cls):
    project = Project.objects.create(name="Test Project")
    Project.objects.create(
        name="Test Project #2", description='Testing Project Number 2'
    )

    TemporaryUser.objects.create(
        username="TestUser", project=project
    )

@classmethod
def tearDownClass(cls):
    Project.objects.all().delete()

def test_created_user_exists(self):
    """Tests if a user is unique per project"""

    response_1 = self.client.get(
        'http://testserver/api/projects/1/temp_user/TestUser/exists/',
        format='json'
    )

    response_2 = self.client.get(
        'http://testserver/api/projects/2/temp_user/TestUser/exists/',
        format='json'
    )

    self.assertEqual(response_1.status_code, 200)
    self.assertEqual(response_2.status_code, 200)

    self.assertJSONEqual(response_1.content, {"exists": True})
    self.assertJSONEqual(response_2.content, {"exists": False})

def test_user_created_for_specific_project(self):
    """Test if a user is created and exists for a specific project"""

    response_1 = self.client.post(
        'http://testserver/api/temp_user/create/',
        format='json',
        data={"username": "TestUser2", "project_id": 2}
    )

    user = TemporaryUser.objects.get(username='TestUser2')

    self.assertEqual(response_1.status_code, 200)
    self.assertEqual(json.loads(response_1.content)['created'], True)

    self.assertEqual(len(user.hash_id), 15)
    self.assertEqual(user.username, "TestUser2")

    response_2 = self.client.get(
        'http://testserver/api/projects/2/temp_user/TestUser2/exists/',
        format='json'
    )

    self.assertEqual(response_2.status_code, 200)
    self.assertJSONEqual(response_2.content, {"exists": True})

def test_user_not_created_twice(self):
    """Test a user can't be created twice"""

    response_1 = self.client.post(
        'http://testserver/api/temp_user/create/',
        format='json',
        data={"username": "TestUser3", "project_id": 1}
    )

    response_2 = self.client.post(
        'http://testserver/api/temp_user/create/',
        format='json',
        data={"username": "TestUser3", "project_id": 1}
    )

    self.assertEqual(response_1.status_code, 200)
    self.assertEqual(response_2.status_code, 200)

    self.assertEqual(json.loads(response_1.content)['created'], True)
    self.assertEqual(json.loads(response_2.content)['created'], False)

当我分别运行./manage.py test core.tests.test_users./manage.py test core.tests.test_projects之类的这两个测试时,它们按预期工作,没有任何错误。

但是当我运行./manage.py test core.tests时,它在test_user_not_created_twice()和test_user_created_for_specific_project()上失败,

  

项目匹配查询不存在

怎么了?我什至在tearDownClass方法中添加了Project.objects.all()。delete(),但这没有帮助。 而且,指定这样的setUpClass方法是一种好习惯吗?

1 个答案:

答案 0 :(得分:0)

由于测试中的某些模型不在示例中,所以我无法进行测试,但是我认为很可能是您所引用的硬编码Project pk导致了问题-在同时运行这两个程序时测试用例,第一个将设置并删除带有pk#1的项目,然后第二个将设置并删除带有pk#2的项目

您可以通过在reset_sequences = True类上设置APITestCase来解决此问题,然后第二个测试用例将再次使用pk#1创建一个项目。

https://docs.djangoproject.com/en/2.2/topics/testing/advanced/#django.test.TransactionTestCase.reset_sequences

更好的做法是通过在测试设置代码中进行Project来保留对self.project = Project.objects.create(name="Test Project")的引用,然后使用self.project.pk而不是对pk {{1}进行硬编码}在您的测试用例中。

对于您的用例(这里是setUpTestData),您也应该考虑使用the source code for both而不是1。在任何设置下添加到数据库中的数据都不需要拆卸,因为这一切都发生在事务中,该事务在类中的所有测试用例运行后都将还原。

假定单个测试读取了该类级别的状态,但未对其进行修改,因此您的测试setUpClass可能会导致其他测试失败,因为它会删除在该类级别创建的对象。