IIQ Testing Framework

Our IIQCommon library (the proprietary half) contains a robust testing framework allowing both online and offline IIQ testing. Tests can executed automatically as part of your CI/CD pipeline or manually as part of your QA process.

This post will give a brief overview of some of the options available.

Online testing

IIQCommon contains an “online” testing framework, meaning that tests are executed within a live instance of IIQ, usually a QA environment.

Tests are implemented in Beanshell using a standard Custom object structure. The configuration is derived from JUnit and should be familiar to anybody who has written a Java test. 

Information can be passed between the various Beanshell components of a test suite, if required, so that your setup and cleanup code can generate random, unique identity or account for testing purposes.

Since test suites are standard IIQ “Custom” objects, they are stored in your ordinary source control repository and deployed along with other IIQ artifacts.

				
					<Custom name="My Test Suite">
    <Attributes>
        <Map>
            <entry name="beforeAll">
                <!-- setup for tests -->
            </entry>
            <entry name="afterAll">
                <!-- cleanup from tests -->
            </entry>
            <entry name="beforeEach">
                <!-- reset before each test runs -->
            </entry>
            <entry name="afterEach">
                <!-- cleanup or verify after each test runs -->
            </entry>
            <entry name="test:My Test Case">
                <!-- your test code here -->
            </entry>
            <entry name="test:My Other Test Case">
                <!-- your test code here -->
            </entry>
        </Map>
    </Attributes>
    
</Custom>
				
			

The test executor is a standard IIQ scheduled task, which can be scheduled or executed like any other. It accepts a list of Custom objects structured like the one above, then runs each test suite in turn. Like JUnit, if a test throws an exception or returns a failure token, it is considered a failure. Otherwise, it passes.

Tests can effectively do anything they want, but the power of the overall iiq-common library makes common operations trivial:

  • Aggregate of bulk or single accounts
  • Refresh individual identities
  • Launch workflows and extract outputs
  • Invoke Perform Maintenance
  • Compare values in a variety of different data types
  • Execute filters or queries against objects, allowing simple string matching of outcomes

Utilities included with iiq-common-testing also allow you to:

  • Construct test Identities, Accounts, and other data structures using a fluent API.
  • Randomize test values, such as names.
  • “Mock” applications, allowing you to run provisioning tests without actually invoking the real connector.
  • Assert outcomes, automating the “throw an exception” part of your testing. This assertion utility includes everything from “assertNotNull” to “assertQueryReturnsSomething“.

Test outcomes are rendered in readable HTML format in the TaskResult. The task itself will succeed or fail depending on whether any tests have failed. In the case of failures, additional error messages may be logged.

Task Result for an IIQ task called "Example test" showing several tests that have PASS or FAIL annotations

Offline testing

IIQCommon offers true offline testing of your IIQ codebase, meaning that no running IIQ instance is required. By implementing mock versions of certain critical IIQ classes, such as SailPointContext, the offline testing framework can run tests against nearly all IIQ code – without starting IIQ!

Offline tests are implemented as standard JUnit tests, using a special annotation to bootstrap the mock IIQ context and all of your IIQ artifacts into place. Since JUnit is an industry standard, you can execute your tests using Maven, Gradle, Ant, or whatever build tool you’d like, and then incorporate the test results directly into your build output.

This framework is quite robust; we use the testing framework to test IIQCommon itself within its Gradle build!

Mock applications

Provisioning connectors do work properly within offline tests, assuming your testing host can reach the target system. You can run real provisioning and aggregation operations against real systems, if you would like.

However, this isn’t always ideal. Your test may run in an isolated container, or you may not have an appropriate test instance of the target.

Offline tests can create “application mocks”, which are real IIQ Application objects, a special proxy and IntegrationConfig attributes injected by IIQCommon. (The implementations are IIQCommon-specific, but proxies and integration configs are native IIQ behavior.)

Mock applications allow you to extract the actual provisioning plans passed to the connector, then simulate a connector aggregation with delimited file data.

IIQCommon also supplies a mock IQService, which simulates responses from a real IQService according to a callback you create.

Example

The following is a real offline JUnit test, part of IIQCommon’s own self-test suite. It bootstraps your IIQ environment using the contents of the “build/extract” folder, which is where your artifacts would be located post-build if you are using SailPoint’s SSB framework. You can then run arbitrary IIQ code, and make various JUnit assertions.

This example also demonstrates mocking and provisioning to an existing application from your codebase, one called “Delimited File” in this case.

				
					@ExtendWith(IIQTestExtension.class)
@InitBasePath("build/extract")
public class Test1 {

    /**
     * This is auto-injected by the extension
     */
    @Inject
    private SailPointContext context;
    
    
    @Inject
    private TestUtil testUtil;

    @Test
    public void testProvisionDelimitedFile() throws GeneralException, ConnectorException {
        Identity identity = context.getObjectByName(Identity.class, "user.amy");
        assertNotNull(identity);

        Application delimitedFile = context.getObjectByName(Application.class, "Delimited File");
        testUtil.getApplicationMock().mockAndSave(delimitedFile);

        ProvisioningUtilities utils = new ProvisioningUtilities(context);
        utils.setUseWorkflow(false);
        ProvisioningPlan plan = new ProvisioningPlan();
        plan.setArguments(new Attributes<>());
        plan.setIdentity(identity);
        plan.add("Delimited File", "user.amy", ProvisioningPlan.AccountRequest.Operation.Create);
        utils.doProvisioning(plan);

        context.saveObject(identity);

        assertEquals(3, identity.getLinks().size());
        assertEquals("Delimited File", identity.getLinks().get(2).getApplicationName());
        assertNotNull(identity.getLinks().get(2).getId());
    }


}