Skip to content

Commit a7cd9aa

Browse files
authored
test: add integration tests and test dependencies (#14)
* test: add assertion for resource version in integration tests - Add integration test for axum-otel middleware - Add unit tests for resource creation - Add dev-dependencies (tokio, reqwest, tower) to axum-otel for testing
1 parent f316917 commit a7cd9aa

File tree

5 files changed

+176
-0
lines changed

5 files changed

+176
-0
lines changed

Cargo.lock

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ axum-otel = { path = "crates/axum-otel", version = "0.31.5" }
4040
config = { version = "0.14.0", default-features = false }
4141
dotenvy = { version = "0.15.7" }
4242
http = { version = "1.3.1" }
43+
http-body-util = { version = "0.1" }
4344
opentelemetry = { version = "0.31.0", default-features = false }
4445
opentelemetry-appender-tracing = { version = "0.31.0" }
4546
opentelemetry-http = { version = "0.31.0", default-features = false }

crates/axum-otel/Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,12 @@ opentelemetry = { workspace = true }
1717
tower-http = { workspace = true }
1818
tracing = { workspace = true }
1919
tracing-otel-extra = { workspace = true, features = ["macros"] }
20+
21+
[dev-dependencies]
22+
http-body-util = { workspace = true }
23+
opentelemetry_sdk = { workspace = true, features = ["testing"] }
24+
reqwest = { workspace = true }
25+
tokio = { workspace = true }
26+
tower = { workspace = true }
27+
tracing-opentelemetry = { workspace = true }
28+
tracing-subscriber = { workspace = true, features = ["registry"] }
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
use axum::{
2+
Router,
3+
body::Body,
4+
http::{Method, Request, StatusCode},
5+
routing::get,
6+
};
7+
use axum_otel::{AxumOtelOnFailure, AxumOtelOnResponse, AxumOtelSpanCreator, Level};
8+
use http_body_util::BodyExt;
9+
use opentelemetry::{global, trace::TracerProvider};
10+
use opentelemetry_sdk::{
11+
Resource,
12+
trace::{InMemorySpanExporter, RandomIdGenerator, Sampler, SdkTracerProvider},
13+
};
14+
use tower::ServiceExt;
15+
use tower_http::trace::TraceLayer;
16+
use tracing::instrument;
17+
use tracing_subscriber::{Registry, layer::SubscriberExt, util::SubscriberInitExt};
18+
19+
#[instrument]
20+
async fn hello() -> &'static str {
21+
"Hello, world!"
22+
}
23+
24+
fn app() -> Router<()> {
25+
Router::new().route("/", get(hello)).layer(
26+
TraceLayer::new_for_http()
27+
.make_span_with(AxumOtelSpanCreator::new().level(Level::INFO))
28+
.on_response(AxumOtelOnResponse::new().level(Level::INFO))
29+
.on_failure(AxumOtelOnFailure::new()),
30+
)
31+
}
32+
33+
#[tokio::test]
34+
async fn test_axum_otel_middleware() {
35+
// Set up in-memory exporter for testing
36+
let exporter = InMemorySpanExporter::default();
37+
let provider: SdkTracerProvider = SdkTracerProvider::builder()
38+
.with_sampler(Sampler::AlwaysOn)
39+
.with_id_generator(RandomIdGenerator::default())
40+
.with_simple_exporter(exporter.clone())
41+
.with_resource(Resource::builder().build())
42+
.build();
43+
44+
global::set_tracer_provider(provider.clone());
45+
46+
// Set up tracing subscriber with OpenTelemetry layer
47+
let tracer = provider.tracer("axum-otel-test".to_string());
48+
let otel_layer = tracing_opentelemetry::OpenTelemetryLayer::new(tracer);
49+
Registry::default()
50+
.with(otel_layer)
51+
.try_init()
52+
.expect("Failed to initialize tracing subscriber");
53+
54+
let app = app();
55+
56+
// Send request using oneshot
57+
let response = app
58+
.oneshot(
59+
Request::builder()
60+
.uri("/")
61+
.method(Method::GET)
62+
.body(Body::empty())
63+
.expect("Failed to build request"),
64+
)
65+
.await
66+
.expect("Failed to send request");
67+
68+
assert_eq!(response.status(), StatusCode::OK);
69+
70+
let body = response
71+
.into_body()
72+
.collect()
73+
.await
74+
.expect("Failed to read body");
75+
76+
assert_eq!(body.to_bytes(), "Hello, world!".as_bytes());
77+
78+
// Force flush to ensure spans are exported
79+
let _ = provider.force_flush();
80+
81+
// Verify spans were created
82+
let spans = exporter
83+
.get_finished_spans()
84+
.expect("Failed to get finished spans");
85+
86+
assert!(
87+
!spans.is_empty(),
88+
"Expected at least one span to be created"
89+
);
90+
91+
// With oneshot(), there's no MatchedPath, so span name is just "GET"
92+
// When using a real server, it would be "GET /"
93+
let request_span = spans
94+
.iter()
95+
.find(|s| s.name == "GET" || s.name == "GET /")
96+
.expect("Request span not found");
97+
98+
let hello_span = spans
99+
.iter()
100+
.find(|s| s.name == "hello")
101+
.expect("Handler span not found");
102+
103+
assert_eq!(
104+
hello_span.parent_span_id,
105+
request_span.span_context.span_id(),
106+
"Handler span should be a child of the request span"
107+
);
108+
109+
// Check http.status_code attribute
110+
let status_code_attr = request_span
111+
.attributes
112+
.iter()
113+
.find(|kv| kv.key.as_str() == "http.status_code")
114+
.map(|kv| kv.value.to_string());
115+
116+
assert_eq!(
117+
status_code_attr,
118+
Some("200".to_string()),
119+
"Expected http.status_code to be 200"
120+
);
121+
122+
provider
123+
.shutdown()
124+
.expect("Failed to shutdown tracer provider");
125+
}

crates/tracing-opentelemetry/src/resource.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,35 @@ pub fn get_resource(service_name: &str, attributes: &[KeyValue]) -> Resource {
2828
.with_attributes(attributes.to_vec())
2929
.build()
3030
}
31+
32+
#[cfg(test)]
33+
mod tests {
34+
use super::get_resource;
35+
use opentelemetry::KeyValue;
36+
37+
#[test]
38+
fn test_get_resource() {
39+
let service_name = "test-service";
40+
let attributes = vec![
41+
KeyValue::new("env", "test"),
42+
KeyValue::new("version", "1.0.0"),
43+
];
44+
45+
let resource = get_resource(service_name, &attributes);
46+
47+
assert_eq!(
48+
resource.get(&opentelemetry::Key::new("service.name")),
49+
Some(opentelemetry::Value::String(service_name.into()))
50+
);
51+
52+
assert_eq!(
53+
resource.get(&opentelemetry::Key::new("env")),
54+
Some(opentelemetry::Value::String("test".into()))
55+
);
56+
57+
assert_eq!(
58+
resource.get(&opentelemetry::Key::new("version")),
59+
Some(opentelemetry::Value::String("1.0.0".into()))
60+
);
61+
}
62+
}

0 commit comments

Comments
 (0)