首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何设计具有测试友好性的Axum服务器?

如何设计具有测试友好性的Axum服务器?
EN

Stack Overflow用户
提问于 2021-10-02 07:53:10
回答 1查看 1.9K关注 0票数 3

当我试图用构建一个应用程序时,我未能将框架与处理程序分开。使用Go,典型的方法是定义一个Interface,实现它并将处理程序注册到框架中。通过这种方式,很容易提供一个模拟处理程序来进行测试。然而,我无法使它与Axum一起工作。我定义了一个和上面一样的trait,但是它不会编译:

代码语言:javascript
复制
use std::net::ToSocketAddrs;
use std::sync::{Arc, Mutex};
use serde_derive::{Serialize, Deserialize};
use serde_json::json;
use axum::{Server, Router, Json};

use axum::extract::Extension;
use axum::routing::BoxRoute;
use axum::handler::get;

#[tokio::main]
async fn main() {
    let app = new_router(
        Foo{}
    );
    axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
        .serve(app.into_make_service())
        .await
        .unwrap();
}

trait Handler {
    fn get(&self, get: GetRequest) -> Result<GetResponse, String>;
}

struct Foo {}

impl Handler for Foo {
    fn get(&self, req: GetRequest) -> Result<GetResponse, String> {
        Ok(GetResponse{ message: "It works.".to_owned()})
    }
}

fn new_router<T:Handler>(handler: T) -> Router<BoxRoute> {
    Router::new()
        .route("/", get(helper))
        .boxed()
}

fn helper<T:Handler>(
    Extension(mut handler): Extension<T>,
    Json(req): Json<GetRequest>
) -> Json<GetResponse> {
    Json(handler.get(req).unwrap())
}

#[derive(Debug, Serialize, Deserialize)]
struct GetRequest {
    // omited
}

#[derive(Debug, Serialize, Deserialize)]
struct GetResponse {
    message: String
    // omited
}
代码语言:javascript
复制
error[E0599]: the method `boxed` exists for struct `Router<axum::routing::Layered<Trace<axum::routing::Layered<AddExtension<Nested<Router<BoxRoute>, Route<axum::handler::OnMethod<fn() -> impl Future {direct}, _, (), EmptyRouter>, EmptyRouter<_>>>, T>>, SharedClassifier<ServerErrorsAsFailures>>>>`, but its trait bounds were not satisfied
   --> src/router.rs:25:10
    |
25  |         .boxed()
    |          ^^^^^ method cannot be called on `Router<axum::routing::Layered<Trace<axum::routing::Layered<AddExtension<Nested<Router<BoxRoute>, Route<axum::handler::OnMethod<fn() -> impl Future {direct}, _, (), EmptyRouter>, EmptyRouter<_>>>, T>>, SharedClassifier<ServerErrorsAsFailures>>>>` due to unsatisfied trait bounds
    | 
   ::: /Users/lebrancebw/.cargo/registry/src/github.com-1ecc6299db9ec823/axum-0.2.5/src/routing/mod.rs:876:1
    |
876 | pub struct Layered<S> {
    | --------------------- doesn't satisfy `<_ as tower_service::Service<Request<_>>>::Error = _`
    |
    = note: the following trait bounds were not satisfied:
            `<axum::routing::Layered<Trace<axum::routing::Layered<AddExtension<Nested<Router<BoxRoute>, Route<axum::handler::OnMethod<fn() -> impl Future {direct}, _, (), EmptyRouter>, EmptyRouter<_>>>, T>>, SharedClassifier<ServerErrorsAsFailures>>> as tower_service::Service<Request<_>>>::Error = _`

我想关键是我的设计显然不是“乡土”。是否有一种方法可以构造一个Axum项目来方便地进行测试?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2022-01-09 15:15:19

问题是你想测试什么。我假设您有一些核心逻辑和一个HTTP层。你要确保:

  1. 请求的路由正确;
  2. 正确解析请求;
  3. 用期望的参数调用核心逻辑;
  4. 来自核心的返回值被正确地格式化为HTTP响应。

要测试它,您需要生成一个核心逻辑模拟出来的服务器实例。@lukemathwalker在他的博客和“Rust中的零到生产”一书中很好地描述了如何生成一个应用程序通过实际的TCP端口进行测试。它是为Actix编写的,但这个想法也适用于Axum。

您不应该使用axum::Server::bind,而是使用axum::Server::from_tcp传递给它一个std::net::TcpListner,它允许您使用‘`TcpListener::bind("127.0.0.1:0")在任何可用端口上生成一个测试服务器。

为了使核心逻辑可注入(和可模拟),我将其声明为一个结构,并实现其上的所有业务方法。如下所示:

代码语言:javascript
复制
pub struct Core {
    public_url: Url,
    secret_key: String,
    storage: Storage,
    client: SomeThirdPartyClient,
}

impl Core {
    pub async fn create_something(
        &self,
        new_something: NewSomething,
    ) -> Result<Something, BusinessErr> {
    ...
}

有了所有这些片段,您就可以编写一个函数来启动服务器:

代码语言:javascript
复制
pub async fn run(listener: TcpListener, core: Core)

这个函数应该封装一些东西,比如路由配置、服务器日志配置等等。

可以使用以下扩展层机制将核心提供给处理程序:

代码语言:javascript
复制
...
let shared_core = Arc::new(core);
...
let app = Router::new()
    .route("/something", post(post_something))
    ...
    .layer(AddExtensionLayer::new(shared_core));

可以使用扩展提取器在参数列表中声明处理程序中的以下内容:

代码语言:javascript
复制
async fn post_something(
    Extension(core): Extension<Arc<Core>>,
    Json(new_something): Json<NewSomething>,
) -> impl IntoResponse {
    core
        .create_something(new_something)
        .await
}

Axum示例包含一个关于错误处理和依赖注入的示例。你可以检查一下,这里

最后但并非最不重要的一点是,现在您可以使用像mockall这样的库来模拟Core,编写将返回运行服务器的主机和端口的spawn_app函数,对它运行一些请求并执行断言。

来自Bogdan的视频提供了一个很好的开场白。

如果你觉得答案中遗漏了什么,我很乐意提供更多的细节。

票数 7
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/69415050

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档