
常用 UT
Properties UT
public final class NacosPropertiesTest extends AbstractConfigurationTest {
@Test
public void testNacosPropertiesDefault() {
load(NacosPropertiesTest.NacosPropertiesConfiguration.class);
NacosProperties nacosProperties = getContext().getBean(NacosProperties.class);
assertNull(nacosProperties.getUrl());
assertNull(nacosProperties.getNamespace());
assertNull(nacosProperties.getAcm());
}
@Test
public void testNacosPropertiesSpecified() {
final String url = "localhost:8848";
final String namespace = "1c10d748-af86-43b9-8265-75f487d20c6c";
NacosProperties.NacosACMProperties acm = new NacosProperties.NacosACMProperties();
acm.setEnabled(false);
acm.setEndpoint("acm.aliyun.com");
load(NacosPropertiesTest.NacosPropertiesConfiguration.class, "soul.sync.nacos.url=localhost:8848",
"soul.sync.nacos.namespace=1c10d748-af86-43b9-8265-75f487d20c6c",
"soul.sync.nacos.acm.enabled=false",
"soul.sync.nacos.acm.endpoint=acm.aliyun.com");
NacosProperties nacosProperties = getContext().getBean(NacosProperties.class);
assertEquals(nacosProperties.getUrl(), url);
assertEquals(nacosProperties.getNamespace(), namespace);
assertEquals(nacosProperties.getAcm().isEnabled(), acm.isEnabled());
assertEquals(nacosProperties.getAcm().getEndpoint(), acm.getEndpoint());
}
@Configuration
@EnableConfigurationProperties(NacosProperties.class)
static class NacosPropertiesConfiguration {
}
}
public abstract class AbstractConfigurationTest {
private final AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
public AnnotationConfigApplicationContext getContext() {
return context;
}
@AfterEach
public void clear() {
context.close();
}
public void load(final Class<?> configuration, final String... inlinedProperties) {
load(new Class<?>[]{configuration}, inlinedProperties);
}
public void load(final Class<?>[] configuration, final String... inlinedProperties) {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, inlinedProperties);
this.context.register(configuration);
this.context.refresh();
}
}
Controller UT
@RunWith(MockitoJUnitRunner.class)
public final class ConfigControllerTest {
private MockMvc mockMvc;
@InjectMocks
private ConfigController configController;
@Mock
private HttpLongPollingDataChangedListener mockLongPollingListener;
@Before
public void setUp() {
this.mockMvc = MockMvcBuilders.standaloneSetup(configController).build();
}
@Test
public void testFetchConfigs() throws Exception {
final ConfigData<?> configData = new ConfigData<>("md5-value1", 0L, Collections.emptyList());
doReturn(configData).when(mockLongPollingListener).fetchConfig(ConfigGroupEnum.APP_AUTH);
final MockHttpServletResponse response = mockMvc.perform(get("/configs/fetch")
.param("groupKeys", new String[]{ConfigGroupEnum.APP_AUTH.toString()})
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.message", is(SoulResultMessage.SUCCESS)))
.andExpect(jsonPath("$.data['APP_AUTH'].md5", is("md5-value1")))
.andReturn().getResponse();
assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value());
}
@Test
public void testListener() throws Exception {
final MockHttpServletResponse response = mockMvc.perform(post("/configs/listener")
.accept(MediaType.APPLICATION_JSON))
.andReturn().getResponse();
assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value());
verify(mockLongPollingListener).doLongPolling(any(HttpServletRequest.class), any(HttpServletResponse.class));
}
}
SpringBoot-starter UT
@RunWith(SpringRunner.class)
@SpringBootTest(
classes = {
SoulAlibabaDubboClientConfiguration.class,
SoulAlibabaDubboClientConfigurationTest.class
},
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
properties = {
"soul.dubbo.adminUrl=http://localhost:9095",
"soul.dubbo.contextPath=/dubbo",
"soul.dubbo.appName=dubbo"
})
@EnableAutoConfiguration
public final class SoulAlibabaDubboClientConfigurationTest {
@Autowired
private DubboConfig dubboConfig;
@Test
public void testDubboConfig() {
assertThat(dubboConfig.getAppName(), is("dubbo"));
assertThat(dubboConfig.getContextPath(), is("/dubbo"));
assertThat(dubboConfig.getAdminUrl(), is("http://localhost:9095"));
}
}
配置类 Starter
@ConditionalOnClass({LoadBalancerClient.class, RibbonAutoConfiguration.class, DispatcherHandler.class})
@AutoConfigureAfter(RibbonAutoConfiguration.class)
@ConditionalOnBean(LoadBalancerClient.class)
@Configuration
public class SpringCloudPluginConfiguration {
@Bean
public SoulPlugin springCloudPlugin(final ObjectProvider<LoadBalancerClient> loadBalancerClient) {
return new SpringCloudPlugin(loadBalancerClient.getIfAvailable());
}
}
public class SpringCloudPluginConfigurationTest {
@Test
public void testSpringCloudPlugin() {
new ApplicationContextRunner()
.withConfiguration(
AutoConfigurations.of(
RibbonAutoConfiguration.class,
DispatcherHandler.class,
RibbonClientConfiguration.class,
SpringCloudPluginConfiguration.class
))
.withPropertyValues(
"debug=true",
"spring.main.banner-mode=off",
"ribbon.client.name=test")
.run(
context -> {
assertThat(context).hasSingleBean(LoadBalancerClient.class);
assertThat(context).hasSingleBean(RibbonAutoConfiguration.class);
assertThat(context).hasSingleBean(DispatcherHandler.class);
assertThat(context).getBean(LoadBalancerClient.class).isInstanceOf(RibbonLoadBalancerClient.class);
SoulPlugin plugin = context.getBean(SoulPlugin.class);
assertThat(plugin.named()).isEqualTo(PluginEnum.SPRING_CLOUD.getName());
}
);
}
}
一些补充
过滤干扰方法
@RunWith(SpringRunner.class)
@SpringBootTest(
classes = {
SoulSpringCloudClientConfiguration.class,
SoulSpringCloudClientConfigurationTest.class
},
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
properties = {
"spring.application.name=spring-cloud-server",
"soul.springcloud.adminUrl=http://localhost:9095",
"soul.springcloud.contextPath=spring-cloud-server",
"soul.springcloud.full=true"
}
)
@EnableAutoConfiguration
public final class SoulSpringCloudClientConfigurationTest {
@Autowired
private SoulSpringCloudConfig soulSpringCloudConfig;
@BeforeClass
public static void before() {
MockedStatic<RegisterUtils> registerUtilsMockedStatic = mockStatic(RegisterUtils.class);
registerUtilsMockedStatic.when(() -> RegisterUtils.doRegister(any(), any(), any())).thenAnswer((Answer<Void>) invocation -> null);
}
@Test
public void testSoulSpringCloudConfig() {
assertThat(soulSpringCloudConfig.getContextPath(), is("spring-cloud-server"));
assertThat(soulSpringCloudConfig.getAdminUrl(), is("http://localhost:9095"));
Assertions.assertThat(soulSpringCloudConfig.isFull()).isTrue();
}
}
静态类
public final class RegisterUtilsTest {
private OkHttpTools okHttpTools;
private String json;
private String url;
@Before
public void setUp() {
okHttpTools = mock(OkHttpTools.class);
Map<String, Object> jsonMap = new HashMap<String, Object>() {
{
put("appName", "dubbo");
put("contextPath", "/dubbo");
put("path", "/dubbo/findByArrayIdsAndName");
put("pathDesc", "");
put("serviceName", "org.dromara.soul.examples.dubbo.api.service.DubboMultiParamService");
put("ruleName", "/dubbo/findByArrayIdsAndName");
put("parameterTypes", "[Ljava.lang.Integer;,java.lang.String");
put("rpcExt", "{\"group\":\"\",\"version\":\"\",\"loadbalance\":\"random\",\"retries\":2,\"timeout\":10000,\"url\":\"\"}");
put("enabled", true);
}
};
json = JsonUtils.toJson(jsonMap);
url = "http://localhost:9095/soul-client/dubbo-register";
}
@SneakyThrows
@Test
public void testDoRegisterWhenThrowException() {
when(okHttpTools.post(url, json)).thenThrow(IOException.class);
try (MockedStatic<OkHttpTools> okHttpToolsMockedStatic = mockStatic(OkHttpTools.class)) {
okHttpToolsMockedStatic.when(OkHttpTools::getInstance).thenReturn(okHttpTools);
RegisterUtils.doRegister(json, url, RpcTypeEnum.DUBBO);
verify(okHttpTools, times(1)).post(eq(url), eq(json));
}
}
}
批量 Set Get
public abstract class AbstractReflectGetterSetterTest {
private static final Map<Class<?>, Object> DEFAULT_MAPPERS;
static {
DEFAULT_MAPPERS = new HashMap<>();
DEFAULT_MAPPERS.put(int.class, 0);
DEFAULT_MAPPERS.put(double.class, 0.0d);
DEFAULT_MAPPERS.put(float.class, 0.0f);
DEFAULT_MAPPERS.put(long.class, 0L);
DEFAULT_MAPPERS.put(boolean.class, true);
DEFAULT_MAPPERS.put(short.class, 0);
DEFAULT_MAPPERS.put(byte.class, 0);
DEFAULT_MAPPERS.put(char.class, 0);
DEFAULT_MAPPERS.put(Integer.class, 0);
DEFAULT_MAPPERS.put(Double.class, 0.0);
DEFAULT_MAPPERS.put(Float.class, 0.0f);
DEFAULT_MAPPERS.put(Long.class, 0L);
DEFAULT_MAPPERS.put(Boolean.class, Boolean.TRUE);
DEFAULT_MAPPERS.put(Short.class, (short) 0);
DEFAULT_MAPPERS.put(Byte.class, (byte) 0);
DEFAULT_MAPPERS.put(Character.class, (char) 0);
DEFAULT_MAPPERS.put(BigDecimal.class, BigDecimal.ONE);
DEFAULT_MAPPERS.put(Date.class, new Date());
DEFAULT_MAPPERS.put(Timestamp.class, new Timestamp(System.currentTimeMillis()));
DEFAULT_MAPPERS.put(Set.class, Collections.emptySet());
DEFAULT_MAPPERS.put(SortedSet.class, Collections.emptySortedSet());
DEFAULT_MAPPERS.put(List.class, Collections.emptyList());
DEFAULT_MAPPERS.put(Map.class, Collections.emptyMap());
DEFAULT_MAPPERS.put(SortedMap.class, Collections.emptySortedMap());
}
protected abstract Class<?> getTargetClass();
protected Set<String> getExcludeFields() {
return null;
}
@Test
public void testGetAndSet() throws Exception {
Class<?> clazz = getTargetClass();
Object target = clazz.getDeclaredConstructor().newInstance();
Field[] fields = clazz.getDeclaredFields();
Set<String> excludeFields = getExcludeFields();
Stream.of(fields)
.forEach(f -> {
if (f.isSynthetic()) {
return;
}
if (excludeFields != null && excludeFields.contains(f.getName())) {
return;
}
try {
PropertyDescriptor property = new PropertyDescriptor(f.getName(), clazz);
Method getter = property.getReadMethod();
Method setter = property.getWriteMethod();
final Object setValue = defaultValue(property.getPropertyType());
setter.invoke(target, setValue);
final Object getValue = getter.invoke(target);
assertEquals(setValue, getValue,
property.getDisplayName() + " getter / setter do not produce the same result."
);
} catch (IntrospectionException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException("", e);
}
});
}
@SneakyThrows
private Object defaultValue(final Class<?> clazz) {
final Object obj = DEFAULT_MAPPERS.get(clazz);
if (obj != null) {
return obj;
}
return clazz.newInstance();
}
}
拦截 HTTP 请求
public final class OkHttpToolsTest {
@Rule
public WireMockRule wireMockRule = new WireMockRule(WireMockConfiguration.wireMockConfig().dynamicPort(), false);
private String url;
private final String json = "{\"appName\":\"soul\"}";
@Before
public void setUpWireMock() {
wireMockRule.stubFor(post(urlPathEqualTo("/"))
.willReturn(aResponse()
.withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString())
.withBody(json)
.withStatus(200))
);
url = "http://localhost:" + wireMockRule.port();
}
@Test
public void testPostReturnString() throws IOException {
assertThat(json, is(OkHttpTools.getInstance().post(url, json)));
}
@Test
public void testPostReturnClassT() throws IOException {
assertThat(GsonUtils.getInstance().fromJson(json, HttpRegisterDTO.class),
equalTo(OkHttpTools.getInstance().post(url, json, HttpRegisterDTO.class)));
}
@Test
public void testPostReturnMap() throws IOException {
final Map<String, String> map = new HashMap<>();
map.put("appName", "soul");
assertThat(json, is(OkHttpTools.getInstance().post(url, map)));
}
@Test
public void testGetGson() {
assertThat(OkHttpTools.getInstance().getGson(), notNullValue());
}
}
单例
public final class UUIDUtilsTest {
@Test
public void testGetInstance() {
UUIDUtils uuidUtils = UUIDUtils.getInstance();
Assert.assertNotNull(uuidUtils);
}
@Test
public void testGenerateShortUuid() {
String shortUuid = UUIDUtils.getInstance().generateShortUuid();
Assert.assertTrue(StringUtils.isNotEmpty(shortUuid));
Assert.assertEquals(19, shortUuid.length());
}
@Test
public void testConstructor() throws Exception {
Class<?> uUIDUtilsClass = UUIDUtils.getInstance().getClass();
Class<?>[] p = {long.class, long.class, long.class};
Constructor<?> constructor = uUIDUtilsClass.getDeclaredConstructor(p);
constructor.setAccessible(true);
try {
constructor.newInstance(-1L, 10L, 10L);
} catch (InvocationTargetException ex) {
Assert.assertTrue(ex.getCause().getMessage().startsWith("worker Id can't be greater than"));
}
try {
constructor.newInstance(10L, -1L, 10L);
} catch (InvocationTargetException ex) {
Assert.assertTrue(ex.getCause().getMessage().startsWith("datacenter Id can't be greater than"));
}
}
@Test
public void testTilNextMillis() throws Exception {
Class<?> uUIDUtilsClass = UUIDUtils.getInstance().getClass();
Class<?>[] p = {long.class};
Method method = uUIDUtilsClass.getDeclaredMethod("tilNextMillis", p);
method.setAccessible(true);
long lastTimestamp = System.currentTimeMillis();
long result = (long) method.invoke(UUIDUtils.getInstance(), lastTimestamp);
assertThat(result, greaterThan(lastTimestamp));
}
@Test
public void testNextIdException() throws Exception {
UUIDUtils uuidUtils = UUIDUtils.getInstance();
Class<?> uUIDUtilsClass = uuidUtils.getClass();
Field field = uUIDUtilsClass.getDeclaredField("lastTimestamp");
field.setAccessible(true);
field.set(uuidUtils, 1617757060000L);
Method method = uUIDUtilsClass.getDeclaredMethod("nextId");
method.setAccessible(true);
try {
method.invoke(UUIDUtils.getInstance());
} catch (InvocationTargetException ex) {
Assert.assertTrue(ex.getCause().getMessage().startsWith("Clock moved backwards."));
}
}
@Test
public void testNextId() throws Exception {
UUIDUtils uuidUtils = UUIDUtils.getInstance();
Class<?> uUIDUtilsClass = uuidUtils.getClass();
Field field = uUIDUtilsClass.getDeclaredField("lastTimestamp");
field.setAccessible(true);
field.set(uuidUtils, System.currentTimeMillis());
Method method = uUIDUtilsClass.getDeclaredMethod("nextId");
method.setAccessible(true);
method.invoke(UUIDUtils.getInstance());
}
}
SPI 相关
public final class ExtensionLoaderTest {
@Test
public void testSPI() {
JdbcSPI jdbcSPI = ExtensionLoader.getExtensionLoader(JdbcSPI.class).getJoin("mysql");
assertThat(jdbcSPI.getClass().getName(), is(MysqlSPI.class.getName()));
}
@Test
public void testSPIGetDefaultJoin() {
HasDefaultSPI spi = ExtensionLoader.getExtensionLoader(HasDefaultSPI.class).getDefaultJoin();
assert spi != null;
assertThat(spi.getClass().getName(), is(SubHasDefaultSPI.class.getName()));
}
@Test
public void testSPINoDefaultJoin() {
JdbcSPI jdbcSPI = ExtensionLoader.getExtensionLoader(JdbcSPI.class).getDefaultJoin();
assertNull(jdbcSPI);
}
@Test
public void testSPIGetJoinNameIsBlank() {
try {
ExtensionLoader.getExtensionLoader(JdbcSPI.class).getJoin("");
fail();
} catch (NullPointerException expected) {
assertThat(expected.getMessage(), containsString("get join name is null"));
}
}
@Test
public void testGetExtensionLoaderIsNull() {
try {
ExtensionLoader.getExtensionLoader(null);
fail();
} catch (NullPointerException expected) {
assertThat(expected.getMessage(), containsString("extension clazz is null"));
}
}
@Test
public void testGetExtensionLoaderNotInterface() {
try {
ExtensionLoader.getExtensionLoader(ExtensionLoaderTest.class);
fail();
} catch (IllegalArgumentException expected) {
assertThat(expected.getMessage(),
containsString("extension clazz (class org.dromara.soul.spi.ExtensionLoaderTest) is not interface!"));
}
}
@Test
public void testGetExtensionLoaderNotSpiAnnotation() {
try {
ExtensionLoader.getExtensionLoader(NopSPI.class);
fail();
} catch (IllegalArgumentException expected) {
assertThat(expected.getMessage(),
containsString("extension clazz (interface org.dromara.soul.spi.fixture.NopSPI) without @interface org.dromara.soul.spi.SPI Annotation"));
}
}
@Test
public void testGetExtensionLoaderNonentitySPIName() {
try {
ExtensionLoader.getExtensionLoader(JdbcSPI.class).getJoin("nonentitySPIName");
fail();
} catch (IllegalArgumentException expected) {
assertThat(expected.getMessage(), containsString("name is error"));
}
}
@Test
public void testGetExtensionLoaderSPISubTypeNotMatchInterface() {
try {
ExtensionLoader.getExtensionLoader(NotMatchSPI.class).getJoin("subNoJoinSPI");
fail();
} catch (IllegalStateException expected) {
assertThat(expected.getMessage(),
containsString("load extension resources error,class org.dromara.soul.spi.fixture.SubNoJoinSPI subtype is not of interface org.dromara.soul.spi.fixture.NotMatchSPI"));
}
}
@Test
public void testGetExtensionLoaderNoClassMatchSPI() {
try {
ExtensionLoader.getExtensionLoader(NoClassMatchSPI.class).getJoin("subNoClassMatchSPI");
fail();
} catch (IllegalStateException expected) {
assertThat(expected.getMessage(), containsString("load extension resources error"));
}
}
@Test
public void testGetExtensionLoaderNoJoinSPI() {
try {
ExtensionLoader.getExtensionLoader(NoJoinSPI.class).getJoin("subNoJoinSPI");
fail();
} catch (IllegalStateException expected) {
assertThat(expected.getMessage(), containsString("load extension resources error,class org.dromara.soul.spi.fixture.SubNoJoinSPI with Join annotation"));
}
}
@Test
public void testGetExtensionLoaderCanNotInstantiatedSPI() {
try {
ExtensionLoader.getExtensionLoader(JdbcSPI.class).getJoin("canNotInstantiated");
fail();
} catch (IllegalStateException expected) {
assertThat(expected.getMessage(), containsString(
"Extension instance(name: canNotInstantiated, class: class org.dromara.soul.spi.fixture.CanNotInstantiatedSPI) "
+ "could not be instantiated: Class org.dromara.soul.spi.ExtensionLoader "
+ "can not access a member of class org.dromara.soul.spi.fixture.CanNotInstantiatedSPI with modifiers \"private\""));
}
}
@Test
public void testLoadClassDuplicateKey() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Method loadClassMethod = getLoadClassMethod();
ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader(JdbcSPI.class);
Map<String, Class<?>> classes = new HashMap<>();
loadClassMethod.invoke(extensionLoader, classes, "mysql", "org.dromara.soul.spi.fixture.MysqlSPI");
try {
loadClassMethod.invoke(extensionLoader, classes, "mysql", "org.dromara.soul.spi.fixture.OracleSPI");
fail();
} catch (InvocationTargetException expect) {
assertThat(expect.getTargetException().getMessage(), containsString(
"load extension resources error,Duplicate class org.dromara.soul.spi.fixture.JdbcSPI name mysql on "
+ "org.dromara.soul.spi.fixture.MysqlSPI or org.dromara.soul.spi.fixture.OracleSPI"));
}
}
@Test
public void loadResourcesIOException()
throws NoSuchMethodException, MalformedURLException, IllegalAccessException {
Method loadResourcesMethod = getLoadResources();
ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader(JdbcSPI.class);
try {
loadResourcesMethod.invoke(extensionLoader, new HashMap<>(),
new URL("file:/org.dromara.soul.spi.fixture.NoExistSPI"));
fail();
} catch (InvocationTargetException expect) {
assertThat(expect.getTargetException().getMessage(), containsString("load extension resources error"));
}
}
private Method getLoadClassMethod() throws NoSuchMethodException {
Method method = ExtensionLoader.class.getDeclaredMethod("loadClass", Map.class, String.class, String.class);
method.setAccessible(true);
return method;
}
private Method getLoadResources() throws NoSuchMethodException {
Method method = ExtensionLoader.class.getDeclaredMethod("loadResources", Map.class, URL.class);
method.setAccessible(true);
return method;
}
}
字节码生成类
Class<?> prxClazz = classDefinition.annotateType(AnnotationDescription.Builder.ofType(Servant.class).build())
.make()
.load(Servant.class.getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
.getLoaded();
assert communicator != null;
prxClassCache.put(metaData.getServiceName(), prxClazz);
@RunWith(MockitoJUnitRunner.class)
public final class ApplicationConfigCacheTest {
private ApplicationConfigCache applicationConfigCacheUnderTest;
@Before
public void setUp() {
applicationConfigCacheUnderTest = ApplicationConfigCache.getInstance();
}
@Test
public void testGet() throws ClassNotFoundException {
final String rpcExt = "{\"methodInfo\":[{\"methodName\":\"method1\",\"params\":"
+ "[{\"left\":\"int\",\"right\":\"param1\"},{\"left\":\"java.lang.Integer\","
+ "\"right\":\"param2\"}],\"returnType\":\"java.lang.String\"}]}";
final MetaData metaData = new MetaData("id", "127.0.0.1:8080", "contextPath",
"path5", RpcTypeEnum.TARS.getName(), "serviceName5", "method1",
"parameterTypes", rpcExt, false);
applicationConfigCacheUnderTest.initPrx(metaData);
final TarsInvokePrxList result = applicationConfigCacheUnderTest.get("path5");
assertNotNull(result);
assertEquals("promise_method1", result.getMethod().getName());
assertEquals(2, result.getParamTypes().length);
assertEquals(2, result.getParamNames().length);
Class<?> prxClazz = Class.forName(PrxInfoUtil.getPrxName(metaData));
assertTrue(Arrays.stream(prxClazz.getAnnotations()).anyMatch(annotation -> annotation instanceof Servant));
}
}