An End-to-End Testing framework of Nginx Configurations.
Below are example tests and assertions that can be made for an Nginx configuration.
Tests are setup by starting the Nginx environment. You need to declare your upstream servers and then start the Nginx configuration.
The code snipped below shows a common pattern of starting and stopping the nginx cluster for tests. This snippet comes from src/test/java/com/kroger/snowGlobe/integration/tests/AppUrlTest
public NginxRpBuilder nginxReverseProxy;
public AppServiceCluster loginUpstreamApp = makeHttpsWebService("Login_Cluster");
@Before
public void setup() {
nginxReverseProxy = NginxRpBuilder.runNginxWithUpstreams(loginUpstreamApp);
}
Below are example test scenarios that include configuration and testing examples. All of the examples below are used as integration tests for the project and pass for each build.
There are times where nginx or the upstream server set the HTTP response code and we can verify the response code whether it is set in Nginx or the upstream server.
Simple Example
This is an example that verifies that the upstream server that sends a 200 will return back to the client with a 200.
Example Nginx code snippet (from src/integrationTestNginxConfig/nginx.conf):
location /login {
proxy_set_header X-Forwarded-Proto https;
proxy_set_header host $host;
proxy_pass https://Login_Cluster/login-path;
}
Example test code snippet (from src/test/java/com/kroger/snowGlobe/integration/tests/StatusCodeTest.java)
@Test
public void should_return_200_for_login() {
make(getRequest("https://www.nginx-test.com/login").to(nginxReverseProxy))
.andExpectResponseCode(200);
}
This is an example that an http call will return a HTTP 301
and have a location header to use HTTPS
Example Nginx code snippet (from src/integrationTestNginxConfig/nginx.conf
):
server {
listen *:80;
server_name
www.nginx-test.com;
location / {
return 301 https://$host$request_uri;
}
}
Example test code snippet (from src/test/java/com/kroger/snowGlobe/integration/tests/StatusCodeTest.java
)
@Test
public void should_return_301_http_to_https() {
make(getRequest("http://www.nginx-test.com").to(nginxReverseProxy))
.andExpectResponseHeader("Location", "https://www.nginx-test.com/")
.andExpectResponseCode(301);
}
Successful Health Check
This is an example that a health check is defined and called in addition to your base call. If you do not define a health check endpoint and call expectSuccessfulHealthCheck you will receive a fail() message
Example Nginx code snippet (from src/integrationTestNginxConfig/nginx.conf
):
server {
listen *:80;
server_name
www.nginx-test.com;
location / {
proxy_set_header host $host;
proxy_pass http://Content_Cluster;
}
location /healthcheck {
return 200;
}
}
Example test code snippet (from src/test/java/com/kroger/snowGlobe/integration/tests/HealthCheckTest.java
)
@Test
public void should_have_successful_health_check() {
make(getRequest("https://www.nginx-test.com")
.withHealthCheck("/healthcheck")
.to(nginxReverseProxy))
.andExpectClusterName("Content_Cluster")
.expectSuccessfulHealthCheck();
}
When Nginx routes a call using the proxy_pass
directive, this test can verify that it was sent to the correct cluster.
Simple Example
This verifies that the login calls are routed to the login cluster. We have another test that verifies that it is routed to the item cluster
Example Nginx code snippet (from src/integrationTestNginxConfig/nginx.conf
):
location /login {
proxy_set_header X-Forwarded-Proto https;
proxy_set_header host $host;
proxy_pass https://Login_Cluster/login-path;
}
location /item {
proxy_set_header host $host;
proxy_pass http://Item_Cluster/item;
}
Example test code snippet (from src/test/java/com/kroger/snowGlobe/integration/tests/ClusterNameTest.java
)
@Test
public void should_route_login_request_to_login_cluster() {
make(getRequest("https://www.nginx-test.com/login").to(nginxReverseProxy))
.andExpectClusterName("Login_Cluster");
}
@Test
public void should_route_item_request_to_item_cluster() {
make(getRequest("https://www.nginx-test.com/item").to(nginxReverseProxy))
.andExpectClusterName("Item_Cluster");
}
When Nginx routes a call using the proxy_pass
directive, this test can verify that it was sent to the upstream app with
the correct path.
Simple Example
This verifies that the login calls are routed with the upstream path of “/login-path”.
Example Nginx code snippet (from src/integrationTestNginxConfig/nginx.conf
):
location /login {
proxy_set_header X-Forwarded-Proto https;
proxy_set_header host $host;
proxy_pass https://Login_Cluster/login-path;
}
Example test code snippet (from src/test/java/com/kroger/snowGlobe/integration/tests/AppPathTest.java
)
@Test
public void should_route_login_request_to_login_path() {
make(getRequest("https://www.nginx-test.com/login").to(nginxReverseProxy))
.andExpectAppPath("/login-path");
}
When Nginx routes a call using the proxy_pass
directive, this test can verify that it was sent to the upstream app with
the a specific header and the value matches a regex pattern. This also verifies that the host header field is properly
set using a dynamic variable.
Simple Example
This verifies that the login calls are routed with the “x-forwarded-proto” header field sent with a value of “https”
Example Nginx code snippet (from src/integrationTestNginxConfig/nginx.conf
):
location /login {
proxy_set_header X-Forwarded-Proto https;
proxy_set_header host $host;
proxy_pass https://Login_Cluster/login-path;
}
Example test code snippet (from src/test/java/com/kroger/snowGlobe/integration/tests/RequestHeaderTest.java
)
@Test
public void should_add_x_proto_header_to_login_request() {
make(getRequest("https://www.nginx-test.com/login").to(nginxReverseProxy))
.andExpectRequestHeaderToApplicationMatching("x-forwarded-proto", "https")
.andExpectRequestHeaderToApplicationMatching("host", "www.nginx-test.com");
}
When Nginx routes a call using the proxy_pass
directive, this test can verify that it was sent to the upstream app with
the host, protocol, and url field.
Simple Example
This verifies that the login calls are routed with the with the host, protocol and url fields properly set
Example Nginx code snippet (from src/integrationTestNginxConfig/nginx.conf
):
location /login {
proxy_set_header X-Forwarded-Proto https;
proxy_set_header host $host;
proxy_pass https://Login_Cluster/login-path;
}
Example test code snippet (from src/test/java/com/kroger/snowGlobe/integration/tests/AppUrlTest.java
)
@Test
public void should_properly_pass_url_fields() {
make(getRequest("https://www.nginx-test.com/login").to(nginxReverseProxy))
.andExpectAppUrl("https://www.nginx-test.com/login-path");
}
When Nginx routes a call using the proxy_pass
directive, this test can verify that the response contains specific
headers. One interesting thing to note is that you can configure your upstream bounce servers to respond with specific
headers. Another test is that you might want Nginx to remove a response header.
Simple Example
This verifies that the that the response header contains specific fields and values that can be populated by the upstream
response or from the nginx configuration. The second test verifies that response does not contain a response header
that was generated by the upstream server, but the proxy_hide_header
directive suppresses it.
Example Nginx code snippet (from src/integrationTestNginxConfig/nginx.conf
):
location /checkout {
proxy_set_header X-Forwarded-Proto https;
proxy_set_header host $host;
add_header rp-response-header true;
proxy_hide_header internal-secret-key;
proxy_pass https://Cart_Cluster/cart/checkout;
}
Example test code snippet (from src/test/java/com/kroger/snowGlobe/integration/tests/ResponseHeaderTest.java
)
// A custom upstream app can be created that defines response headers.
public static AppServiceCluster cartUpstreamApp = makeHttpsWebService("Cart_Cluster", 1)
.withResponseHeader("got-cart", "success")
.withResponseHeader("internal-secret-key", "42");
//...
@Test
public void should_return_response_headers() {
make(getRequest("https://www.nginx-test.com/checkout").to(nginxReverseProxy))
.andExpectResponseHeader("got-cart", "success") // this comes from the fake upstream
.andExpectResponseHeader("rp-response-header", "true"); // this comes from the Nginx Configuration
}
@Test
public void should_not_return_secret_header() {
make(getRequest("https://www.nginx-test.com/checkout").to(nginxReverseProxy))
.andExpectMissingResponseHeader("internal-secret_key");
}
@Test
public void should_have_matching_response_header() {
make(getRequest("https://www.nginx-test.com/checkout").to(nginxReverseProxy))
.andExpectResponseHeaderMatches("internal-secret-key", "[0-9]+");
}
When Nginx routes a call using the proxy_pass
directive, this test can verify that the request is sent to a specific
upstream instance. This is useful to verify the weighting, and load distribution configuration.
Simple Example
This verifies that the calls are made in a round robin distribution.
Example Nginx code snippet (from src/integrationTestNginxConfig/nginx.conf
):
location /login {
proxy_set_header X-Forwarded-Proto https;
proxy_set_header host $host;
proxy_pass https://Login_Cluster/login-path;
}
Example test code snippet (from src/test/java/com/kroger/snowGlobe/integration/tests/ClusterNumberTest.java
)
// A custom upstream app can be created that defines response headers.
public static AppServiceCluster cartUpstreamApp = makeHttpsWebService("Cart_Cluster", 1)
.withResponseHeader("got-cart", "success");
//...
@Test
public void should_round_robin_each_request_to_each_upstream_instance() {
range(0,10).forEach(clusterNumber ->
make(getRequest("https://www.nginx-test.com/login").to(nginxReverseProxy))
.andExpectClusterNumber(clusterNumber));
}
When Nginx routes a call using the proxy_pass
directive, this test can verify that the request is sent to an upstream
service has a specified query param set.
Simple Example
This verifies that a path variable from a regex expression is converted to a query parameter for an upstream service.
Example Nginx code snippet (from src/integrationTestNginxConfig/nginx.conf
):
location ~* /search/(.*) {
proxy_pass http://Search_Cluster/search?q=$1;
}
Example test code snippet (from src/test/java/com/kroger/snowGlobe/integration/tests/QueryParamTest.java
)
@Test
public void should_convert_path_to_query_param() {
make(getRequest("https://www.nginx-test.com/search/milk").to(nginxReverseProxy))
.andExpectToHaveQueryParam("q", "milk");
}
When Nginx routes a call using the proxy_pass
directive, this test can verify that response matches a string value or
the contents of a file.
Simple Example
This verifies that /body
matches the file in src/integrationTestNginxConfig/static/static.html
and the string
contents
Example Nginx code snippet (from src/integrationTestNginxConfig/nginx.conf
):
location /body {
try_files /static.html =404;
}
Example test code snippet (from src/test/java/com/kroger/snowGlobe/integration/tests/FileResponseTest.java
)
@Test
public void should_return_response_headers() {
make(getRequest("https://www.nginx-test.com/checkout").to(nginxReverseProxy))
.andExpectResponseHeader("got-cart", "success")
.andExpectResponseHeader("rp-response-header", "true");
}
@Test
public void should_not_return_secret_header() {
make(getRequest("https://www.nginx-test.com/checkout").to(nginxReverseProxy))
.andExpectMissingResponseHeader("internal-secret_key");
}