Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 5.3

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.  Please note the comments // NOTE # which indicates what has been added to this file.

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.
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);
        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);
        when(_processor.getType()).thenReturn("mock");
        when(_accountDao.findByIdIncludingRemoved(0L)).thenReturn(acct);

        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() {
        when(_groupDao.isNameInUse(anyLong(), anyLong(), eq("group1"))).thenReturn(true);
        AffinityGroup group2 = _affinityService.createAffinityGroup("user", domainId, "group1", "mock",
                "affinity group two");
    }

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

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

    @Test(expected = InvalidParameterValueException.class)
    public void deleteAffinityGroupNullIdName() throws ResourceInUseException {
        _affinityService.deleteAffinityGroup(null, "user", domainId, null);
    }

    @Test(expected = ResourceInUseException.class)
    public void deleteAffinityGroupInUse() throws ResourceInUseException {
        List<AffinityGroupVMMapVO> affinityGroupVmMap = new ArrayList<AffinityGroupVMMapVO>();
        AffinityGroupVMMapVO mapVO = new AffinityGroupVMMapVO(20L, 10L);
        affinityGroupVmMap.add(mapVO);
        when(_affinityGroupVMMapDao.listByAffinityGroup(20L)).thenReturn(affinityGroupVmMap);

        AffinityGroupVO groupVO = new AffinityGroupVO();
        when(_groupDao.findById(20L)).thenReturn(groupVO);
        when(_groupDao.lockRow(20L, true)).thenReturn(groupVO);

        _affinityService.deleteAffinityGroup(20L, "user", domainId, null);
    }

    @Test(expected = InvalidParameterValueException.class)
    public void updateAffinityGroupVMRunning() throws ResourceInUseException {

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

        List<Long> affinityGroupIds = new ArrayList<Long>();
        affinityGroupIds.add(20L);

        _affinityService.updateVMAffinityGroups(10L, affinityGroupIds);
    }

    @Configuration
    @ComponentScan(basePackageClasses = {AffinityGroupServiceImpl.class, EventUtils.class}, includeFilters = {@Filter(value = TestConfiguration.Library.class, type = FilterType.CUSTOM)}, useDefaultFilters = false)
    public static class TestConfiguration extends SpringUtils.CloudStackTestConfiguration { // NOTE #4

        @Bean
        public AccountDao accountDao() {
            return Mockito.mock(AccountDao.class);
        }

        @Bean
        public AccountService accountService() {
            return Mockito.mock(AccountService.class);
        }

        @Bean
        public AffinityGroupProcessor affinityGroupProcessor() {
            return Mockito.mock(AffinityGroupProcessor.class);
        }

        @Bean
        public AffinityGroupDao affinityGroupDao() {
            return Mockito.mock(AffinityGroupDao.class);
        }

        @Bean
        public AffinityGroupVMMapDao affinityGroupVMMapDao() {
            return Mockito.mock(AffinityGroupVMMapDao.class);
        }

        @Bean
        public AccountManager accountManager() {
            return Mockito.mock(AccountManager.class);
        }

        @Bean
        public EventDao eventDao() {
            return Mockito.mock(EventDao.class);
        }

        @Bean
        public UserVmDao userVMDao() {
            return Mockito.mock(UserVmDao.class);
        }

        public static class Library implements TypeFilter {

            @Override
            public boolean match(MetadataReader mdr, MetadataReaderFactory arg1) throws IOException {
                ComponentScan cs = TestConfiguration.class.getAnnotation(ComponentScan.class);
                return SpringUtils.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.

 

Besides these, if the method you need to unit test has @DB annotation, then you also need to make the following changes:

  • Add the following dependency to your pom.xml:

    <dependency>

      <groupId>org.apache.cloudstack</groupId>

      <artifactId>cloud-api</artifactId>

      <version>${project.version}</version>

      <type>test-jar</type>

      <scope>test</scope>

    </dependency>    

  • Create a "test/resources" folder with a db.properties file that can be copied from utils/conf/db.properties.