Actix-web: Support functions returning an App or similar trait

Created on 4 Dec 2019  路  10Comments  路  Source: actix/actix-web

To support testing the same app it would be nice if there was a way to return an App or an HttpService from a function.

It doesn't seem possible in either stable 1.0.9 or what is in master to make a function that returns an App since AppEntry doesn't seem to get forwarded publicly anywhere. I think it might be possible if App::new() is called and then passed in but the problem remains the bounds on the impl for App is fairly complicated and seems to rely on traits that are not re-exported in actix-web::dev or anywhere else.

I suspect this might also partially be a documentation issue? If there is a way I can open a PR to update the docs (assuming someone points me in the right direction).

Most helpful comment

Leaving this here for when these folks return once their App factory pattern stops working following their migration to actix-web 2.0. The following pattern works. return the call to init_service, as illustrated.

pub fn create_test_app() 
    -> impl Future<Output=impl Service<Request = Request, 
                                       Response = ServiceResponse<impl MessageBody>,
                                       Error = AWError>>
{
...
    test::init_service(
         App::new()
             ...
    )
}

All 10 comments

Even if AppEntry would be in dev any new middleware would change type of app

@fafhrd91 I spoke up in another issue about this. I'll reply to your answer which says:

Even if AppEntry would be in dev any new middleware would change type of app

I am fine with that. I just want to be able to centralize creation of the App in a function so I'm not copying and pasting App initialization code in all of my test functions. The latter is more costly than the App type changing because it only gets mentioned in one place.

Am I doing something wrong? How should I write tests that talk to my Actix app? The first few lines of every test function look like this:

#[test]
pub fn test_creates_two_log_messages() {
    let conf = config::get_env();
    let conf_copy = conf.clone();
    let oauth_context = auth::initialize_context(&conf_copy);
    let dbexec = config::make_app_state(conf.db);
    let mut app = test::init_service(
        App::new()
            .data(AppState { db: dbexec.clone(), oauth_context: oauth_context.clone(), })
            .configure(router::config_app),
    );

The only work-around that has occurred to me is having a single function with #[test] and invoking sub-tests with App as a parameter.

you can use impl Trait,

fn create_app() -> impl ServiceFactory<
        Config = (),
        Request = Request,
        Response = ServiceResponse<impl MessageBody>,
        Error = impl std::fmt::Debug,
    > {
}

@fafhrd91 Thank you for the help! I think I'm on the right track with your suggestion but it's a little tricky to get right. I was passing the app to test::init_service so I think I have a Service rather than a ServiceFactory. Here's what I have so far:

fn create_test_app() -> (
    db::DbExecutor,
    impl Service<
        Request = Request,
        Response = ServiceResponse<impl MessageBody>,
        Error = impl std::fmt::Debug,
    >,
) {

It's currently complaining that I'm not allowed to import actix_http::request::Request but that's what it wants me to provide in the impl Service arguments I believe. Is it the case that impl ServiceFactory is intended to work but impl Service is not? Or am I just fumbling around here?

I needed to change my import to use actix_http::Request; instead of use actix_http::request::Request; I'm all good now, worked beautifully, thank you @fafhrd91! :champagne:

Thanks for the help. I hit that problem and ended up something like this:

pub fn with_app<'a, T, B>(
    config: config::Config,
    app: App<T, B>,
) -> App<
    impl NewService<
            Config = (),
            Request = ServiceRequest,
            Response = ServiceResponse<B>,
            Error = actix_web::Error,
            InitError = (),
        > + 'a,
    B,
>
where
    B: MessageBody + 'static,
    T: NewService<
            Config = (),
            Request = ServiceRequest,
            Response = ServiceResponse<B>,
            Error = actix_web::Error,
            InitError = (),
        > + 'static,
{
...
}

Those impl traits look much nicer!

@robo-corg I think you can replace T and B with impls. I don't think you need 'a lifetime

Leaving this here for when these folks return once their App factory pattern stops working following their migration to actix-web 2.0. The following pattern works. return the call to init_service, as illustrated.

pub fn create_test_app() 
    -> impl Future<Output=impl Service<Request = Request, 
                                       Response = ServiceResponse<impl MessageBody>,
                                       Error = AWError>>
{
...
    test::init_service(
         App::new()
             ...
    )
}

Even more copy-pastable:

use my_server::configure_app;

use actix_http::Request;
use actix_web::http::StatusCode;
use actix_web::{dev as ax_dev, dev::Service as _, test, App, Error as AxError};

pub async fn test_app() -> impl ax_dev::Service<
    Request = Request,
    Response = ax_dev::ServiceResponse<impl ax_dev::MessageBody>,
    Error = AxError,
> {
    test::init_service(App::new().configure(configure_app)).await
}

#[actix_rt::test]
async fn my_test() {
    let req = test::TestRequest::get().uri("/my_endpoint").to_request();
    let resp = test_app().await.call(req).await.unwrap();
    assert_eq!(resp.status(), StatusCode::OK);
}

@adwhit Tried your code but didn't work because

error[E0432]: unresolved import `actix_http`
  --> src/main.rs:58:9
   |
58 |     use actix_http::Request;
   |         ^^^^^^^^^^ use of undeclared type or module `actix_http`

or if I use use actix_http::request::Request; (including actix_http dependency)

then I get
Screen Shot 2020-05-23 at 17 19 53

@wontheone1 try it as shown i.e.

use actix_http::Request;
Was this page helpful?
0 / 5 - 0 ratings

Related issues

djc picture djc  路  3Comments

icommit picture icommit  路  3Comments

yoshrc picture yoshrc  路  5Comments

zhaobingss picture zhaobingss  路  4Comments

sebzim4500 picture sebzim4500  路  3Comments