CloudStack 4.1 has switched over to using Spring for component injection and aop. This makes it much more flexible in terms of working with Junit. This page details how to write a test. This assumes you're already familiar with JUnit.
Test
If the JUnit test you created is testing a component that requires component injection, you should add the following to the top of the class.
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(loader = AnnotationConfigContextLoader.class)
Within the class, you should add this method. This call makes sure all of the components injected went through the proper life cycle initialization before the test happens.
@Before public void setup() throws Exception { ComponentContext.initComponentsLifeCycle(); }
You can pretty much copy and paste the following xml into your test configuration xml. We're still looking for ways to get rid of the xml file completely. Change the [Test Configuration] to your own test configuration.
<!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you 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. --> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <context:annotation-config /> <!-- @DB support --> <aop:config proxy-target-class="true"> <aop:aspect id="dbContextBuilder" ref="transactionContextBuilder"> <aop:pointcut id="captureAnyMethod" expression="execution(* *(..))" /> <aop:around pointcut-ref="captureAnyMethod" method="AroundAnyMethod" /> </aop:aspect> </aop:config> <bean id="transactionContextBuilder" class="com.cloud.utils.db.TransactionContextBuilder" /> <bean id="componentContext" class="com.cloud.utils.component.ComponentContext"/> <bean id="TestConfiguration" class="[Test Configuration]" /> <bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"> <property name="requiredParameterValue" value="false" /> </bean> </beans>
In the XML above, you specified the class where the test configuration will be. Here's what that test configuration looks like. In this class, any components that's not mocked is specified in the ComponentScan annotation. Any component's that's mocked is specified as a method with the Bean annotation. Now, you might noticed that this class contains a Library class, which is not standard Spring. The ComponentScan annotation automatically scans in the packages not just the class. By using the Library filter, we are limiting the components loaded to only the classes listed in the ComponentScan annotation. This reduces the number of components loaded. If you do want it to scan the package, then don't add the includeFilters attribute to the annotation.
@Configuration @ComponentScan(basePackageClasses={ SecurityGroupRulesDaoImpl.class, UserVmDaoImpl.class, AccountDaoImpl.class, ConfigurationDaoImpl.class, SecurityGroupWorkDaoImpl.class, VmRulesetLogDaoImpl.class, VMInstanceDaoImpl.class, DomainDaoImpl.class, UsageEventDaoImpl.class, ResourceTagsDaoImpl.class, HostDaoImpl.class, HostDetailsDaoImpl.class, HostTagsDaoImpl.class, ClusterDaoImpl.class, HostPodDaoImpl.class, DataCenterDaoImpl.class, DataCenterIpAddressDaoImpl.class, HostTransferMapDaoImpl.class, SecurityGroupManagerImpl2.class, SecurityGroupDaoImpl.class, SecurityGroupVMMapDaoImpl.class, UserVmDetailsDaoImpl.class, DataCenterIpAddressDaoImpl.class, DataCenterLinkLocalIpAddressDaoImpl.class, DataCenterVnetDaoImpl.class, PodVlanDaoImpl.class, DcDetailsDaoImpl.class, SecurityGroupRuleDaoImpl.class, NicDaoImpl.class, SecurityGroupJoinDaoImpl.class}, includeFilters={@Filter(value=SecurityGroupManagerTestConfiguration.Library.class, type=FilterType.CUSTOM)}, useDefaultFilters=false ) public class SecurityGroupManagerTestConfiguration { @Bean public NetworkModel networkModel() { return Mockito.mock(NetworkModel.class); } @Bean public AgentManager agentManager() { return Mockito.mock(AgentManager.class); } @Bean public VirtualMachineManager virtualMachineManager(){ return Mockito.mock(VirtualMachineManager.class); } @Bean public UserVmManager userVmManager() { return Mockito.mock(UserVmManager.class); } @Bean public NetworkManager networkManager(){ return Mockito.mock(NetworkManager.class); } @Bean public AccountManager accountManager() { return Mockito.mock(AccountManager.class); } @Bean public DomainManager domainManager() { return Mockito.mock(DomainManager.class); } @Bean public ProjectManager projectManager() { return Mockito.mock(ProjectManager.class); } public static class Library implements TypeFilter { @Override public boolean match(MetadataReader mdr, MetadataReaderFactory arg1) throws IOException { mdr.getClassMetadata().getClassName(); ComponentScan cs = SecurityGroupManagerTestConfiguration.class.getAnnotation(ComponentScan.class); return SpringComponentScanUtils.includedInBasePackageClasses(mdr.getClassMetadata().getClassName(), cs); } } }
You might also wonder why we prefer to write a java file instead of putting it all in the XML. The problem with XML is refactoring wreaks havoc in the XML based libraries. It's better to specify the Java file and let an editor like Eclipse take care of the refactoring propagation. We're looking to completely eliminate the XML if possible to reduce the number of files to specify for each test.