Back to Repositories

Validating Nacos Service Discovery Integration in Spring Cloud Alibaba

This comprehensive test suite validates Nacos service discovery functionality in Spring Cloud Alibaba, focusing on service registration, discovery, and integration with various client implementations. The tests verify core service discovery capabilities, load balancing, and circuit breaker patterns.

Test Coverage Overview

The test suite provides extensive coverage of Nacos service discovery functionality:

  • Basic service discovery and registration validation
  • RestTemplate integration with load balancing
  • FeignClient implementation testing
  • Sentinel Circuit Breaker integration
  • Actuator endpoint verification

Implementation Analysis

The testing approach utilizes JUnit 5 with TestContainers for Nacos instance management. Tests employ multiple service instances to validate load balancing behavior and service discovery patterns. The implementation leverages Spring Boot’s testing capabilities with custom configuration profiles.

Technical Details

Key technical components include:

  • TestContainers for Nacos container management
  • Spring Boot test framework integration
  • Custom ContainerStarter utility
  • Multiple service profile configurations
  • RestTemplate and FeignClient implementations
  • Sentinel Circuit Breaker integration

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Containerized test environment setup
  • Proper test isolation and cleanup
  • Comprehensive assertion patterns
  • Clear test case organization
  • Effective use of test utilities and support classes

alibaba/spring-cloud-alibaba

spring-cloud-alibaba-tests/nacos-tests/nacos-discovery-test/src/test/java/com/alibaba/cloud/tests/nacos/discovery/NacosDiscoveryTest.java

            
/*
 * Copyright 2013-2023 the original author or 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
 *
 *      https://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.alibaba.cloud.tests.nacos.discovery;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;

import com.alibaba.cloud.testsupport.ContainerStarter;
import com.alibaba.cloud.testsupport.HasDockerAndItEnabled;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper;

import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.context.WebServerApplicationContext;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

import static com.alibaba.cloud.tests.nacos.discovery.NacosDiscoveryTestApp.Service2Client;
import static com.alibaba.cloud.testsupport.Tester.justDo;
import static com.alibaba.cloud.testsupport.Tester.testFunction;
import static java.util.function.Predicate.isEqual;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

/**
 *
 *
 * @author freeman
 */
@HasDockerAndItEnabled
public class NacosDiscoveryTest {

	static GenericContainer nacos;
	static ConfigurableApplicationContext service1 = new SpringApplicationBuilder(
			NacosDiscoveryTestApp.class).profiles("service-1").run();
	static ConfigurableApplicationContext service2_0 = new SpringApplicationBuilder(
			NacosDiscoveryTestApp.class).profiles("service-2").run();
	static ConfigurableApplicationContext service2_1 = new SpringApplicationBuilder(
			NacosDiscoveryTestApp.class).profiles("service-2").run();

	static {
		nacos = ContainerStarter.startNacos();

		// wait nacos started
		justDo(() -> Thread.sleep(4000L));
	}

	static {
		String serverAddr = "localhost:" + nacos.getMappedPort(8848);
		System.setProperty("spring.cloud.nacos.discovery.server-addr", serverAddr);
	}

	@BeforeAll
	static void init() {
		// give nacos a break
		justDo(() -> Thread.sleep(1000L));
	}

	@AfterAll
	static void stop() {
		service1.stop();
		service2_0.stop();
		service2_1.stop();
	}

	@Test
	public void testServiceDiscovery() {
		testFunction("Service discovery", () -> {
			DiscoveryClient discoveryClient = service1.getBean(DiscoveryClient.class);
			List<ServiceInstance> instances = discoveryClient.getInstances("service-2");
			assertThat(instances).hasSize(2);
		});
	}

	@Test
	public void testServiceDiscoveryWithRestTemplate() {
		testFunction("Service discovery with RestTemplate", () -> {
			RestTemplate restTemplate = service1.getBean(RestTemplate.class);
			assertThatThrownBy(() -> service2_0.getBean(RestTemplate.class));

			// default using RR
			List<Object> list1 = new ArrayList<>();
			List<Object> list2 = new ArrayList<>();
			for (int i = 0; i < 20; i++) {
				ResponseEntity<String> response = restTemplate
						.getForEntity("http://service-2", String.class);
				if (i % 2 != 0) {
					list1.add(response.getBody());
				}
				else {
					list2.add(response.getBody());
				}
			}

			assertThat(list1).hasSize(10);
			assertThat(list1.stream().allMatch(isEqual(list1.get(0)))).isTrue();
			assertThat(list2).hasSize(10);
			assertThat(list2.stream().allMatch(isEqual(list2.get(0)))).isTrue();
		});
	}

	@Test
	public void testServiceDiscoveryWithFeignClient() {
		testFunction("Service discovery with FeignClient", () -> {
			Service2Client service2Client = service1.getBean(Service2Client.class);

			// default using RR
			List<Object> list1 = new ArrayList<>();
			List<Object> list2 = new ArrayList<>();
			for (int i = 0; i < 20; i++) {
				Object result = service2Client.get();
				if (i % 2 != 0) {
					list1.add(result);
				}
				else {
					list2.add(result);
				}
			}

			assertThat(list1).hasSize(10);
			assertThat(list1.stream().allMatch(isEqual(list1.get(0)))).isTrue();
			assertThat(list2).hasSize(10);
			assertThat(list2.stream().allMatch(isEqual(list2.get(0)))).isTrue();
		});
	}

	@Test
	public void testServiceDiscoveryWithFeignClientUsingSentinelCircuitBreaker() {
		testFunction("Service discovery with FeignClient using Sentinel Circuit Breaker",
				() -> {
					Service2Client service2Client = service1
							.getBean(Service2Client.class);

					List<Object> passResult = new ArrayList<>();
					List<Object> fallbackResult = new ArrayList<>();
					for (int i = 0; i < 10; i++) {
						passResult.add(service2Client.pass(true));
						fallbackResult.add(service2Client.pass(false));
					}

					assertThat(passResult.stream().allMatch(isEqual("ok"))).isTrue();
					assertThat(fallbackResult.stream().allMatch(isEqual("fallback")))
							.isTrue();
				});
	}

	@Test
	public void testServiceDiscoveryActuatorEndpoint() {
		testFunction("Service discovery actuator endpoint", () -> {

			WebServerApplicationContext webServerApplicationContext = (WebServerApplicationContext) service1;
			int port = webServerApplicationContext.getWebServer().getPort();

			String response = new RestTemplate().getForEntity(
					String.format("http://127.0.0.1:%d/actuator/nacosdiscovery", port),
					String.class).getBody();

			LinkedHashMap map = new ObjectMapper().readValue(response,
					LinkedHashMap.class);

			assertThat(map.containsKey("subscribe")).isTrue();
			assertThat(map.containsKey("NacosDiscoveryProperties")).isTrue();
		});
	}

}