rust Actix-web集成测试:重用主线程应用程序

dsf9zpds  于 2022-12-23  发布在  其他
关注(0)|答案(2)|浏览(147)

我正在使用actix-web编写一个小服务,我正在添加集成测试来评估功能,并注意到在每个测试中,我必须重复与主应用程序中相同的定义,除了它被测试服务 Package :

let app = test::init_service(App::new().service(health_check)).await;

如果您有简单的服务,这可以很容易地扩展,但当中间件和更多配置开始添加时,测试开始变得庞大,此外,可能很容易错过一些东西,而不是评估与主应用程序相同的规格。
我一直试图从主线程中提取App,以便能够在我的测试中重用它,但没有成功。具体来说,我想为App创建一个“工厂”:

pub fn get_app() -> App<????> {
App::new()
            .wrap(Logger::default())
            .wrap(IdentityService::new(policy))
            .service(health_check)
            .service(login)
}

这样我就可以在测试中写

let app = get_app();
let service =  test::init_service(app).await;

但是编译器需要特定的返回类型,它似乎是一个由几个traits和struct组成的chorizo,其中一些是私有的。
有人有过这样的经验吗?
谢谢!

h43kikqp

h43kikqp1#

定义一个声明性宏app!来构建App,但使用过程API定义路由,而不是使用Actix内置宏(如#[get("/")])。
这个例子使用数据库池作为state-您的应用程序可能有不同种类的状态或者根本没有状态。

#[macro_export]
macro_rules! app (
    ($pool: expr) => ({
        App::new()
            .wrap(middleware::Logger::default())
            .app_data(web::Data::new($pool.clone()))
            .route("/health", web::get().to(health_get))
            .service(web::resource("/items")
                .route(web::get().to(items_get))
                .route(web::post().to(items_post))
            )
    });
);

这可在测试中用作:

#[cfg(test)]
mod tests {
    // more code here for get_test_pool
    #[test]
    async fn test_health() {
        let app = test::init_service(app!(get_test_pool().await)).await;

        let req = test::TestRequest::get().uri("/health").to_request();
        let resp = test::call_service(&app, req).await;
        assert!(resp.status().is_success());
    }
}

在主应用程序中为:

// More code here for get_main_pool
#[actix_web::main]
async fn main() -> Result<(),std::io::Error> {
    let pool = get_main_pool().await?;
    HttpServer::new(move || app!(pool))
        .bind(("127.0.0.1", 8080))?
        .run()
        .await
}

在这种情况下,get_main_pool必须返回,比如说,Result<sqlx::Pool<sqlx::Postgres>, std::io::Error>,才能与actix_web::main的签名要求兼容,另一方面,get_test_pool可以简单地返回sqlx::Pool<sqlx::Postgres>

cu6pst1q

cu6pst1q2#

我在使用actix-web@4时也遇到了同样的问题,但我想出了一个可能的解决方案。它可能不太理想,但符合我的需要。我需要在Cargo.toml中引入actix-service@2.0.2actix-http@3.2.2
我创建了一个带有初始化器的test.rs文件,它可以在所有测试中使用。

use actix_web::{test::{self}, App, web, dev::{HttpServiceFactory, ServiceResponse}, Error};
use actix_service::Service;
use actix_http::{Request};

#[cfg(test)]
pub async fn init(service_factory: impl HttpServiceFactory + 'static) -> impl Service<Request, Response = ServiceResponse, Error = Error> {
    // connect to your database or other things to pass to AppState

    test::init_service(
        App::new()
            .app_data(web::Data::new(crate::AppState { db }))
            .service(service_factory)
    ).await
}

我在API服务中使用它来减少集成测试中的样板文件。

// ...

#[get("/")]
async fn get_index() -> impl Responder {
    HttpResponse::Ok().body("Hello, world!")
}

#[cfg(test)]
mod tests {
    use actix_web::{test::TestRequest};

    use super::{get_index};

    #[actix_web::test]
    async fn test_get_index() {
        let mut app = crate::test::init(get_index).await;

        let resp = TestRequest::get().uri("/").send_request(&mut app).await;
        assert!(resp.status().is_success(), "Something went wrong");
    }
}

我相信您遇到的问题是尝试为App(在Actix中有点反模式)而不是init_service创建工厂。如果您想创建一个返回App的函数,我相信首选约定是使用configure。请参阅此问题以获取参考:https://github.com/actix/actix-web/issues/2039 .

相关问题