Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

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 junit 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.

Code Block

@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.

Code Block

    @Before
public void setup() throws Exception {
    ComponentContext.initComponentsLifeCycle();
}

Test Configuration Xml

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
              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() {
        http://www.springframework.org/schema/context
  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);

       <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>

Test Configuration

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= AffinityGroupVO 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);
         UserVmDaoImpl.class,AffinityGroup group2 = _affinityService.createAffinityGroup("user", domainId, "group1", "mock",
                "affinity group two");
    }

    AccountDaoImpl.class,@Test(expected = InvalidParameterValueException.class)
    public void deleteAffinityGroupInvalidIdTest() throws ResourceInUseException {
        ConfigurationDaoImpl.class,when(_groupDao.findById(20L)).thenReturn(null);
        SecurityGroupWorkDaoImpl.class,_affinityService.deleteAffinityGroup(20L, "user", domainId, "group1");
    }

    VmRulesetLogDaoImpl.class,@Test(expected = InvalidParameterValueException.class)
    public void deleteAffinityGroupInvalidIdName() throws ResourceInUseException {
        VMInstanceDaoImpl.class,when(_groupDao.findByAccountAndName(200L, "group1")).thenReturn(null);
        DomainDaoImpl.class,_affinityService.deleteAffinityGroup(null, "user", domainId, "group1");
    }

    UsageEventDaoImpl.class,@Test(expected = InvalidParameterValueException.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>();
         ClusterDaoImpl.class,AffinityGroupVMMapVO mapVO = 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);
    }

    SecurityGroupVMMapDaoImpl@Test(expected = InvalidParameterValueException.class,)
    public void updateAffinityGroupVMRunning()  UserVmDetailsDaoImpl.class,
        DataCenterIpAddressDaoImpl.classthrows ResourceInUseException {

        UserVmVO vm = new UserVmVO(10L, "test", "test", 101L, HypervisorType.Any, 21L, false, false, domainId, 200L,
        DataCenterLinkLocalIpAddressDaoImpl.class,        5L, "", "test", 1L);
        DataCenterVnetDaoImpl.class,vm.setState(VirtualMachine.State.Running);
        PodVlanDaoImpl.class,
when(_vmDao.findById(10L)).thenReturn(vm);

        List<Long> affinityGroupIds =  DcDetailsDaoImpl.class,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.

  • It is annotated with @Configuration.
  • @ComonentScan tells Spring which components should be loaded.  Note that Spring notes all classes in the package of the class specified so it can load too many classes.  To avoid this, specify the includeFilters as in the example above and useDefaultFilters=false in the @ComponentScan annotation.  What this does is makes sure only the classes specified in @ComponentScan is loaded and not other classes in the same package.
  • Extend SpringUtils.CloudStackConfiguration to get all the transaction handling etc.
  • Use Mockito to mock up the components that you don't want to test.
  • Specify the Library if you used includeFilters in the @ComponentScan annotation.  Just copy this class into your TestConfiguration.  Make sure the class is public static.