I'm building an application using microservices with the netflix stack and spring boot. One thing that bugs me is that I have no integration tests yet, where I can mock the surrounding services.
So, I have service A which is a eureka client with ribbon to resolve the eureka name to the URL of a registered service B during a call.
So ideally I want to start the application with the integrationtest annotations of spring boot, use wiremock to simulate the service B and then call the method of service A, this should call my mocked service B using the symbolic name of the service.
Did anyone already solve this? I have searched for blog entries etc. of people doing this already, but couldn't find any...
I know of the SO article Mock an Eureka Feign Client for Unittesting but as far as I can see this just prevents the discovery client from complaining.
Netflix Ribbon is a Part of Netflix Open Source Software (Netflix OSS). It is a cloud library that provides the client-side load balancing. It automatically interacts with Netflix Service Discovery (Eureka) because it is a member of the Netflix family. The Ribbon mainly provides client-side load balancing algorithms.
Eureka is a convenient way to abstract the discovery of remote servers so that you do not have to hard code their URLs in clients. However, if you prefer not to use Eureka, Ribbon and Feign also work. Suppose you have declared a @RibbonClient for "stores", and Eureka is not in use (and not even on the classpath).
Eureka Server is also known as Discovery Server and it contains all the information about client microservices running on which IP address and port. Client-side service discovery allows services to find and communicate with each other without hard-coding the hostname and port.
Test the ApplicationVisit the eureka-client in the browser, at http://localhost:8080/service-instances/a-bootiful-client . There, you should see the ServiceInstance for the eureka-client reflected in the response. If you see an empty <List> element, wait a bit and refresh the page.
The following code (taken from https://github.com/Netflix/eureka/blob/a7a8d278e6399bbff5faa49b9fcbcd7ea9e854f4/eureka-core/src/test/java/com/netflix/eureka/mock/MockRemoteEurekaServer.java) may be helpful to you;
public class MockRemoteEurekaServer extends ExternalResource {
public static final String EUREKA_API_BASE_PATH = "/eureka/v2/";
private final Map<String, Application> applicationMap;
private final Map<String, Application> applicationDeltaMap;
private final Server server;
private boolean sentDelta;
private int port;
private volatile boolean simulateNotReady;
public MockRemoteEurekaServer(int port, Map<String, Application> applicationMap,
Map<String, Application> applicationDeltaMap) {
this.applicationMap = applicationMap;
this.applicationDeltaMap = applicationDeltaMap;
ServletHandler handler = new AppsResourceHandler();
EurekaServerConfig serverConfig = new DefaultEurekaServerConfig();
EurekaServerContext serverContext = mock(EurekaServerContext.class);
when(serverContext.getServerConfig()).thenReturn(serverConfig);
handler.addFilterWithMapping(ServerRequestAuthFilter.class, "/*", 1).setFilter(new ServerRequestAuthFilter(serverContext));
handler.addFilterWithMapping(RateLimitingFilter.class, "/*", 1).setFilter(new RateLimitingFilter(serverContext));
server = new Server(port);
server.addHandler(handler);
System.out.println(String.format(
"Created eureka server mock with applications map %s and applications delta map %s",
stringifyAppMap(applicationMap), stringifyAppMap(applicationDeltaMap)));
}
@Override
protected void before() throws Throwable {
start();
}
@Override
protected void after() {
try {
stop();
} catch (Exception e) {
Assert.fail(e.getMessage());
}
}
public void start() throws Exception {
server.start();
port = server.getConnectors()[0].getLocalPort();
}
public void stop() throws Exception {
server.stop();
}
public boolean isSentDelta() {
return sentDelta;
}
public int getPort() {
return port;
}
public void simulateNotReady(boolean simulateNotReady) {
this.simulateNotReady = simulateNotReady;
}
private static String stringifyAppMap(Map<String, Application> applicationMap) {
StringBuilder builder = new StringBuilder();
for (Map.Entry<String, Application> entry : applicationMap.entrySet()) {
String entryAsString = String.format("{ name : %s , instance count: %d }", entry.getKey(),
entry.getValue().getInstances().size());
builder.append(entryAsString);
}
return builder.toString();
}
private class AppsResourceHandler extends ServletHandler {
@Override
public void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch)
throws IOException, ServletException {
if (simulateNotReady) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
return;
}
String authName = request.getHeader(AbstractEurekaIdentity.AUTH_NAME_HEADER_KEY);
String authVersion = request.getHeader(AbstractEurekaIdentity.AUTH_VERSION_HEADER_KEY);
String authId = request.getHeader(AbstractEurekaIdentity.AUTH_ID_HEADER_KEY);
Assert.assertNotNull(authName);
Assert.assertNotNull(authVersion);
Assert.assertNotNull(authId);
Assert.assertTrue(!authName.equals(ServerRequestAuthFilter.UNKNOWN));
Assert.assertTrue(!authVersion.equals(ServerRequestAuthFilter.UNKNOWN));
Assert.assertTrue(!authId.equals(ServerRequestAuthFilter.UNKNOWN));
for (FilterHolder filterHolder : this.getFilters()) {
filterHolder.getFilter().doFilter(request, response, new FilterChain() {
@Override
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
// do nothing;
}
});
}
String pathInfo = request.getPathInfo();
System.out.println(
"Eureka resource mock, received request on path: " + pathInfo + ". HTTP method: |" + request
.getMethod() + '|');
boolean handled = false;
if (null != pathInfo && pathInfo.startsWith("")) {
pathInfo = pathInfo.substring(EUREKA_API_BASE_PATH.length());
if (pathInfo.startsWith("apps/delta")) {
Applications apps = new Applications();
for (Application application : applicationDeltaMap.values()) {
apps.addApplication(application);
}
apps.setAppsHashCode(apps.getReconcileHashCode());
sendOkResponseWithContent((Request) request, response, toJson(apps));
handled = true;
sentDelta = true;
} else if (pathInfo.startsWith("apps")) {
Applications apps = new Applications();
for (Application application : applicationMap.values()) {
apps.addApplication(application);
}
apps.setAppsHashCode(apps.getReconcileHashCode());
sendOkResponseWithContent((Request) request, response, toJson(apps));
handled = true;
}
}
if (!handled) {
response.sendError(HttpServletResponse.SC_NOT_FOUND,
"Request path: " + pathInfo + " not supported by eureka resource mock.");
}
}
private void sendOkResponseWithContent(Request request, HttpServletResponse response, String content)
throws IOException {
response.setContentType("application/json; charset=UTF-8");
response.setStatus(HttpServletResponse.SC_OK);
response.getOutputStream().write(content.getBytes("UTF-8"));
response.getOutputStream().flush();
request.setHandled(true);
System.out.println("Eureka resource mock, sent response for request path: " + request.getPathInfo() +
" with content" + content);
}
}
private String toJson(Applications apps) throws IOException {
return new EurekaJsonJacksonCodec().getObjectMapper(Applications.class).writeValueAsString(apps);
}
}
One option would be to use Camel to mock/replace the Eureka endpoints. There should be config telling your app where to look for Eureka, so override that in your test configuration to point to new endpoint.
Then create a Camel route in test/src using either jetty or http to represent this new endpoint, which would return a response that the LoadBalancerClient expects. That response would have the URI under test (i.e. your application).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With