Back to Repositories

Testing Sentinel Circuit Breaker Integration in Spring Cloud Alibaba

This integration test suite validates the Sentinel Circuit Breaker implementation in Spring Cloud Alibaba, focusing on degradation rules and fallback behavior. The tests verify circuit breaker functionality under normal and slow response scenarios, ensuring proper fault tolerance mechanisms.

Test Coverage Overview

The test suite provides comprehensive coverage of the Sentinel Circuit Breaker functionality:

  • Response time degradation testing with configurable thresholds
  • Fallback behavior verification during circuit open states
  • Recovery testing after circuit breaker triggers
  • Normal operation validation without degradation

Implementation Analysis

The testing approach utilizes Spring Boot Test framework with a TestRestTemplate for HTTP requests. The implementation demonstrates circuit breaker patterns with:

  • Custom degradation rules configuration
  • Slow response simulation using Thread.sleep
  • Fallback handler implementation
  • Circuit state transition verification

Technical Details

Key technical components include:

  • JUnit Jupiter test framework
  • Spring Boot Test with random port configuration
  • Sentinel DegradeRuleManager for rule management
  • CircuitBreakerFactory customization
  • TestRestTemplate for endpoint testing

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Proper test isolation with @BeforeEach setup and teardown
  • Comprehensive assertion coverage
  • Clear test case separation for different scenarios
  • Proper resource cleanup
  • Configuration externalization

alibaba/spring-cloud-alibaba

spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/test/java/com/alibaba/cloud/circuitbreaker/sentinel/SentinelCircuitBreakerIntegrationTest.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.circuitbreaker.sentinel;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
import org.springframework.cloud.client.circuitbreaker.Customizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;

/**
 * @author Eric Zhao
 */
@SpringBootTest(webEnvironment = RANDOM_PORT,
		classes = SentinelCircuitBreakerIntegrationTest.Application.class,
		properties = { "spring.cloud.discovery.client.health-indicator.enabled=false" })
public class SentinelCircuitBreakerIntegrationTest {

	@Autowired
	private Application.DemoControllerService service;

	@Test
	public void testSlow() throws Exception {
		assertThat(service.slow(true)).isEqualTo("slow");
		assertThat(service.slow(true)).isEqualTo("slow");
		assertThat(service.slow(true)).isEqualTo("slow");
		assertThat(service.slow(false)).isEqualTo("slow");
		assertThat(service.slow(false)).isEqualTo("slow");
		assertThat(service.slow(true)).isEqualTo("slow");
		assertThat(service.slow(true)).isEqualTo("slow");

		// Then in the next 10s, the fallback method should be called.
		for (int i = 0; i < 5; i++) {
			assertThat(service.slow(true)).isEqualTo("fallback");
			Thread.sleep(1000);
		}

		// Try a normal request.
		assertThat(service.slow(false)).isEqualTo("slow");
		// Recovered.
		assertThat(service.slow(true)).isEqualTo("slow");
	}

	@Test
	public void testNormal() {
		assertThat(service.normal()).isEqualTo("normal");
	}

	@BeforeEach
	public void setUp() {
		DegradeRuleManager.loadRules(new ArrayList<>());
	}

	@BeforeEach
	public void tearDown() {
		DegradeRuleManager.loadRules(new ArrayList<>());
	}

	@Configuration
	@EnableAutoConfiguration
	@RestController
	protected static class Application {

		@GetMapping("/slow")
		public String slow(@RequestParam(required = false) Boolean slow)
				throws InterruptedException {
			if (slow == null || slow) {
				Thread.sleep(80);
			}
			return "slow";
		}

		@GetMapping("/normal")
		public String normal() {
			return "normal";
		}

		@Bean
		public Customizer<SentinelCircuitBreakerFactory> slowCustomizer() {
			String slowId = "slow";
			List<DegradeRule> rules = Collections.singletonList(new DegradeRule(slowId)
					.setGrade(RuleConstant.DEGRADE_GRADE_RT).setCount(50)
					.setSlowRatioThreshold(0.7).setMinRequestAmount(5)
					.setStatIntervalMs(30000).setTimeWindow(5));
			return factory -> {
				factory.configure(builder -> builder.rules(rules), slowId);
				factory.configureDefault(id -> new SentinelConfigBuilder()
						.resourceName(id)
						.rules(Collections.singletonList(new DegradeRule(id)
								.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT)
								.setCount(10).setStatIntervalMs(10000).setTimeWindow(10)))
						.build());
			};
		}

		@Service
		public static class DemoControllerService {

			private TestRestTemplate rest;

			private CircuitBreakerFactory cbFactory;

			DemoControllerService(TestRestTemplate rest,
					CircuitBreakerFactory cbFactory) {
				this.rest = rest;
				this.cbFactory = cbFactory;
			}

			public String slow(boolean slow) {
				return cbFactory.create("slow").run(
						() -> rest.getForObject("/slow?slow=" + slow, String.class),
						t -> "fallback");
			}

			public String normal() {
				return cbFactory.create("normal").run(
						() -> rest.getForObject("/normal", String.class),
						t -> "fallback");
			}

		}

	}

}