Back to Repositories

Implementing Jetty Server Integration Testing in Apollo Config

This base integration test class provides core functionality for setting up and managing Jetty server instances in Apollo configuration tests. It handles server lifecycle management, port allocation, and request handling for integration testing scenarios.

Test Coverage Overview

The test suite provides comprehensive coverage for server-side integration testing in Apollo.

  • Server lifecycle management including startup and teardown
  • Dynamic port allocation to prevent conflicts
  • Custom request handling and response mocking
  • Error handling and resource cleanup

Implementation Analysis

The implementation utilizes JUnit’s testing framework combined with Jetty server components for integration testing.

Key patterns include:
  • Abstract base class design for test reusability
  • Context handler configuration for request routing
  • Automated resource management with @After annotations
  • Dynamic server configuration through handler injection

Technical Details

Testing tools and configuration:
  • Jetty Server for HTTP request handling
  • JUnit test framework integration
  • ServerSocket for port management
  • ContextHandlerCollection for request routing
  • AbstractHandler implementation for response mocking

Best Practices Demonstrated

The test suite exemplifies several testing best practices in integration testing.

  • Proper resource cleanup and management
  • Dynamic port allocation to prevent test conflicts
  • Modular and reusable test infrastructure
  • Clear separation of concerns between setup and test logic
  • Robust error handling and logging

apolloconfig/apollo

apollo-portal/src/test/java/com/ctrip/framework/apollo/portal/environment/BaseIntegrationTest.java

            
/*
 * Copyright 2024 Apollo Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package com.ctrip.framework.apollo.portal.environment;

import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.junit.After;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.ServerSocket;

public abstract class BaseIntegrationTest {
  protected static final int PORT = findFreePort();
  private Server server;

  /**
   * init and start a jetty server, remember to call server.stop when the task is finished
   */
  protected Server startServerWithHandlers(ContextHandler... handlers) throws Exception {
    server = new Server(PORT);

    ContextHandlerCollection contexts = new ContextHandlerCollection();
    contexts.setHandlers(handlers);

    server.setHandler(contexts);
    server.start();

    return server;
  }


  @After
  public void tearDown() throws Exception {
    if (server != null && server.isStarted()) {
      server.stop();
    }
  }

  ContextHandler mockServerHandler(final int statusCode, final String response) {
    ContextHandler context = new ContextHandler("/");
    context.setHandler(new AbstractHandler() {

      @Override
      public void handle(String target, Request baseRequest, HttpServletRequest request,
          HttpServletResponse response) throws IOException, ServletException {

        response.setContentType("text/plain;charset=UTF-8");
        response.setStatus(statusCode);
        response.getWriter().println(response);
        baseRequest.setHandled(true);
      }
    });
    return context;
  }

  /**
   * Returns a free port number on localhost.
   *
   * Heavily inspired from org.eclipse.jdt.launching.SocketUtil (to avoid a dependency to JDT just because of this).
   * Slightly improved with close() missing in JDT. And throws exception instead of returning -1.
   *
   * @return a free port number on localhost
   * @throws IllegalStateException if unable to find a free port
   */
  static int findFreePort() {
    ServerSocket socket = null;
    try {
      socket = new ServerSocket(0);
      socket.setReuseAddress(true);
      int port = socket.getLocalPort();
      try {
        socket.close();
      } catch (IOException e) {
        // Ignore IOException on close()
      }
      return port;
    } catch (IOException e) {
    } finally {
      if (socket != null) {
        try {
          socket.close();
        } catch (IOException e) {
        }
      }
    }
    throw new IllegalStateException("Could not find a free TCP/IP port to start embedded Jetty HTTP Server on");
  }
}