In CloudStack 4.1 , we 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 junit test. This assumes you're already familiar with JUnit.
Test
Wiki Markup |
---|
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. Replace the \[configuration.xml\] with the xml that you want to use to configure the components for your test. |
Code Block |
---|
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:/[configuration.xml]")
|
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.
Code Block |
---|
<at:var at:name="Before" />Before
public void setup() throws Exception {
ComponentContext.initComponentsLifeCycle();
}
|
Wiki Markup |
---|
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. |
Please note the comments // NOTE # which indicates what has been added to this file.
Code Block |
---|
// |
Code Block |
<!-- 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 package org.apache.cloudstack.affinity; import ...; @RunWith(SpringJUnit4ClassRunner.class)// NOTE #1 @ContextConfiguration(loader = AnnotationConfigContextLoader.class) // NOTE #2 public class AffinityApiUnitTest { @Inject AffinityGroupService _affinityService; @Inject AccountManager _acctMgr; @Inject AffinityGroupProcessor _processor; @Inject AffinityGroupDao _groupDao; @Inject UserVmDao _vmDao; @Inject AffinityGroupVMMapDao _affinityGroupVMMapDao; @Inject AffinityGroupDao _affinityGroupDao; @Inject EventUtils _eventUtils; @Inject AccountDao _accountDao; @Inject EventDao _eventDao; private static long domainId = 5L; @BeforeClass public static void setUp() throws ConfigurationException { } @Before public void testSetUp() { ComponentContext.initComponentsLifeCycle(); // NOTE #3 AccountVO acct = new AccountVO(200L); 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">acct.setType(Account.ACCOUNT_TYPE_NORMAL); acct.setAccountName("user"); acct.setDomainId(domainId); UserContext.registerContext(1, acct, null, true); when(_acctMgr.finalizeOwner((Account) anyObject(), anyString(), anyLong(), anyLong())).thenReturn(acct); <aop:pointcut id="captureAnyMethod" expression="execution(* *(..))" /> when(_processor.getType()).thenReturn("mock"); when(_accountDao.findByIdIncludingRemoved(0L)).thenReturn(acct); AffinityGroupVO <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.
Code Block |
---|
@Configuration @ComponentScan(basePackageClasses=group = new AffinityGroupVO("group1", "mock", "mock group", domainId, 200L); Mockito.when(_affinityGroupDao.persist(Mockito.any(AffinityGroupVO.class))).thenReturn(group); Mockito.when(_affinityGroupDao.findById(Mockito.anyLong())).thenReturn(group); Mockito.when(_affinityGroupDao.findByAccountAndName(Mockito.anyLong(), Mockito.anyString())).thenReturn(group); Mockito.when(_affinityGroupDao.lockRow(Mockito.anyLong(), anyBoolean())).thenReturn(group); Mockito.when(_affinityGroupDao.expunge(Mockito.anyLong())).thenReturn(true); Mockito.when(_eventDao.persist(Mockito.any(EventVO.class))).thenReturn(new EventVO()); } @Test public void createAffinityGroupTest() { when(_groupDao.isNameInUse(anyLong(), anyLong(), eq("group1"))).thenReturn(false); AffinityGroup group = _affinityService.createAffinityGroup("user", domainId, "group1", "mock", "affinity group one"); assertNotNull("Affinity group 'group1' of type 'mock' failed to create ", group); } @Test(expected = InvalidParameterValueException.class) public void invalidAffinityTypeTest() { AffinityGroup group = _affinityService.createAffinityGroup("user", domainId, "group1", "invalid", "affinity group one"); } @Test(expected = InvalidParameterValueException.class) public void uniqueAffinityNameTest() { SecurityGroupRulesDaoImpl.class, when(_groupDao.isNameInUse(anyLong(), anyLong(), eq("group1"))).thenReturn(true); AffinityGroup group2 = UserVmDaoImpl.class, _affinityService.createAffinityGroup("user", domainId, "group1", "mock", "affinity group two"); } @Test(expected = AccountDaoImplInvalidParameterValueException.class,) public void deleteAffinityGroupInvalidIdTest() throws ResourceInUseException { ConfigurationDaoImpl.class,when(_groupDao.findById(20L)).thenReturn(null); SecurityGroupWorkDaoImpl.class, _affinityService.deleteAffinityGroup(20L, "user", domainId, "group1"); } @Test(expected = VmRulesetLogDaoImplInvalidParameterValueException.class,) public void deleteAffinityGroupInvalidIdName() throws ResourceInUseException { VMInstanceDaoImpl.class,when(_groupDao.findByAccountAndName(200L, "group1")).thenReturn(null); DomainDaoImpl.class, _affinityService.deleteAffinityGroup(null, "user", domainId, "group1"); } @Test(expected = UsageEventDaoImplInvalidParameterValueException.class,) public void deleteAffinityGroupNullIdName() throws ResourceInUseException { ResourceTagsDaoImpl.class, _affinityService.deleteAffinityGroup(null, "user", domainId, null); } @Test(expected = HostDaoImplResourceInUseException.class,) public void deleteAffinityGroupInUse() throws ResourceInUseException HostDetailsDaoImpl.class, { List<AffinityGroupVMMapVO> affinityGroupVmMap = HostTagsDaoImpl.class, new ArrayList<AffinityGroupVMMapVO>(); AffinityGroupVMMapVO mapVO = ClusterDaoImpl.class,new AffinityGroupVMMapVO(20L, 10L); HostPodDaoImpl.class,affinityGroupVmMap.add(mapVO); DataCenterDaoImpl.class, when(_affinityGroupVMMapDao.listByAffinityGroup(20L)).thenReturn(affinityGroupVmMap); AffinityGroupVO groupVO DataCenterIpAddressDaoImpl.class,= new AffinityGroupVO(); HostTransferMapDaoImpl.class,when(_groupDao.findById(20L)).thenReturn(groupVO); SecurityGroupManagerImpl2.class,when(_groupDao.lockRow(20L, true)).thenReturn(groupVO); SecurityGroupDaoImpl.class, _affinityService.deleteAffinityGroup(20L, "user", domainId, null); } @Test(expected = SecurityGroupVMMapDaoImplInvalidParameterValueException.class,) public void updateAffinityGroupVMRunning() throws UserVmDetailsDaoImpl.class, ResourceInUseException { UserVmVO vm = new DataCenterIpAddressDaoImpl.class, UserVmVO(10L, "test", "test", 101L, HypervisorType.Any, 21L, false, false, domainId, 200L, 5L, DataCenterLinkLocalIpAddressDaoImpl.class,"", "test", 1L); DataCenterVnetDaoImpl.class,vm.setState(VirtualMachine.State.Running); PodVlanDaoImpl.class,when(_vmDao.findById(10L)).thenReturn(vm); DcDetailsDaoImpl.class,List<Long> affinityGroupIds = new ArrayList<Long>(); SecurityGroupRuleDaoImpl.class,affinityGroupIds.add(20L); NicDaoImpl.class,_affinityService.updateVMAffinityGroups(10L, affinityGroupIds); } SecurityGroupJoinDaoImpl.class},@Configuration @ComponentScan(basePackageClasses = {AffinityGroupServiceImpl.class, EventUtils.class}, includeFilters = {@Filter(value =SecurityGroupManagerTestConfiguration TestConfiguration.Library.class, type = FilterType.CUSTOM)}, useDefaultFilters = false) public useDefaultFilters=false static class TestConfiguration extends SpringUtils.CloudStackTestConfiguration { // NOTE )#4 public class SecurityGroupManagerTestConfiguration { @Bean public NetworkModelAccountDao networkModelaccountDao() { return Mockito.mock(NetworkModelAccountDao.class); } @Bean public AgentManagerAccountService agentManageraccountService() { return Mockito.mock(AgentManagerAccountService.class); } @Bean public VirtualMachineManagerAffinityGroupProcessor virtualMachineManageraffinityGroupProcessor() { return Mockito.mock(VirtualMachineManagerAffinityGroupProcessor.class); } @Bean public UserVmManagerAffinityGroupDao userVmManageraffinityGroupDao() { return Mockito.mock(UserVmManagerAffinityGroupDao.class); } @Bean public NetworkManagerAffinityGroupVMMapDao networkManageraffinityGroupVMMapDao() { return Mockito.mock(NetworkManagerAffinityGroupVMMapDao.class); } @Bean public AccountManager accountManager() { return Mockito.mock(AccountManager.class); } @Bean public DomainManagerEventDao domainManagereventDao() { return Mockito.mock(DomainManagerEventDao.class); } @Bean public ProjectManagerUserVmDao projectManageruserVMDao() { return Mockito.mock(ProjectManagerUserVmDao.class); } public static class Library implements TypeFilter { @Override public boolean match(MetadataReader mdr, MetadataReaderFactory arg1) throws IOException { mdr.getClassMetadata().getClassName(); ComponentScan cs = SecurityGroupManagerTestConfigurationTestConfiguration.class.getAnnotation(ComponentScan.class); return SpringComponentScanUtilsSpringUtils.includedInBasePackageClasses(mdr.getClassMetadata().getClassName(), cs); } } } } |
...
The above example demonstrates a single self-contained unit test. It not only contains the unit test but also the spring configuration necessary to inject all of the necessary components. The configuration is contained inside the Java class TestConfiguration. Let's go over each note as commented in the code.
NOTE #1: Specifying @RunWith denotes a junit test@RunWith(SpringJUnit4ClassRunner.class)
NOTE #2: ContextConfiguration tells Spring where to find the component configuration for this Junit test. AnnotationConfigContextLoader.class specifies that the test configuration exists within this class.@ContextConfiguration(loader = AnnotationConfigContextLoader.class)
NOTE #3: You need to initComponentsLifeCycle in setup because that causes all of the components injected follows CloudStack's life cycle management.ComponentContext.initComponentsLifeCycle();
NOTE #4: TestConfiguration Class tells Spring the configuration you want to use for this Junit test. Note the following things in the class.
Besides these, if the method you need to unit test has @DB annotation, then you also need to make the following changes:
<dependency>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloud-api</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>