如何使用 Fast API 中的夹具和 pytest 在单个测试中跳过身份验证?

How can I skip authentication in a single test with a fixture in Fast API together with pytest?

我已经构建了类似于 documentation 中描述的身份验证。所以我从那里复制了这个依赖项:

async def get_current_user(token: str = Depends(oauth2_scheme)):
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise credentials_exception
        token_data = TokenData(username=username)
    except JWTError:
        raise credentials_exception
    user = get_user(fake_users_db, username=token_data.username)
    if user is None:
        raise credentials_exception
    return user

我在一组端点中使用,例如,在文档的另一个示例中,对于 User 我有 GETPOSTPUT , DELETEGET ALL.

唯一不需要身份验证的方法是 POST 创建新用户的方法。

我希望能够定义单元测试来验证未经身份验证无法访问的方法,并且当我专注于方法的内容时我想完全跳过身份验证。

因此我在夹具中使用了覆盖功能。例如这个测试:

test_user.py

def test_create_user(test_db, create_user, user, skip_authentication):
    """
    Verify a user can be created and retrieved
    """
    response = client.post(
        "/api/v1/users/",
        json=create_user,
    )

    # Assert creation
    assert response.status_code == 200, response.text
    data = response.json()
    assert "id" in data
    user_id = data["id"]
    del data["id"]
    assert data == user

    # Assert get user
    response = client.get(f"/api/v1/users/{user_id}")
    assert response.status_code == 200, response.text
    data = response.json()
    assert user_id == data["id"]
    del data["id"]
    assert data == user

conftest.py

@pytest.fixture
def skip_authentication() -> None:

    def get_current_user():
        pass
    app.dependency_overrides[get_current_active_user] = get_current_user

这似乎可以删除身份验证,但它在所有测试中都将其删除,而不仅仅是在具有夹具 skip_authentication 的测试中。

如何将其限制为仅我想要的测试?

根据@MatsLindh 的评论,我能够让它工作。我不确定这是否是理想的解决方案,但对我有用。

我创建了两个装置,一个用于经过身份验证的用户,另一个用于其他测试:

conftest.py

@pytest.fixture
def client():
    """
    Return an API Client
    """
    app.dependency_overrides = {}
    return TestClient(app)

@pytest.fixture
def client_authenticated():
    """
    Returns an API client which skips the authentication
    """
    def skip_auth():
        pass
    app.dependency_overrides[get_current_active_user] = skip_auth
    return TestClient(app)

然后我能够测试我的正常测试并验证身份验证:

def test_premissions_user(client, test_db, create_user):
    """
    Verify that not logged in users can not access the user functions excluding create
    """
    # Create user
    response = client.post(
        "/api/v1/users/",
        json=create_user
    )
    assert response.status_code == 200, response.text

    # Get all users
    response = client.get(
        "/api/v1/users/",
    )
    assert response.status_code == 401, response.text
    
    # Get user 1
    response = client.get(
        "/api/v1/users/1",
    )
    assert response.status_code == 401, response.text        
    
    # Delete user 1
    response = client.get(
        "/api/v1/users/1",
    )
    assert response.status_code == 401, response.text    
    
    # Modify user 1
    response = client.delete(
        "/api/v1/users/1",
    )
    assert response.status_code == 401, response.text

def test_premissions_user_authenticated(client_authenticated, test_db, create_user):
    """
    Verify that not logged in users can not access the user functions excluding create
    """
    # Create user
    response = client_authenticated.post(
        "/api/v1/users/",
        json=create_user
    )
    assert response.status_code == 200, response.text

    # Get all users
    response = client_authenticated.get(
        "/api/v1/users/",
    )
    assert response.status_code == 200, response.text
    
    # Get user 1
    response = client_authenticated.get(
        "/api/v1/users/1",
    )
    assert response.status_code == 200, response.text        
    
    # Delete user 1
    response = client_authenticated.get(
        "/api/v1/users/1",
    )
    assert response.status_code == 200, response.text    
    
    # Modify user 1
    response = client_authenticated.delete(
        "/api/v1/users/1",
    )
    assert response.status_code == 204, response.text

我创建了 pytest-fastapi-deps 库,它允许轻松定义和清理 FastAPI 依赖项。

像这样使用它只会影响一个测试:

def test_create_user(test_db, create_user, user, fastapi_dep):
    """
    Verify a user can be created and retrieved
    """
    def skip_auth():
        pass
    with fastapi_dep(app).override({get_current_active_user: skip_auth}):
        response = client.post(
            "/api/v1/users/",
            json=create_user,
        )

        # Assert creation
        assert response.status_code == 200, response.text
        data = response.json()
        assert "id" in data
        user_id = data["id"]
        del data["id"]
        assert data == user

        # Assert get user
        response = client.get(f"/api/v1/users/{user_id}")
        assert response.status_code == 200, response.text
        data = response.json()
        assert user_id == data["id"]
        del data["id"]
        assert data == user