Introduction

Live tests are a test framework based on the JUnit test, which allows to execute integration tests with a running local server.

  • Real functionality can be tested
  • Test assets can be used for test purposes
  • Provides API for testing asset applications and commands
  • Possibility to run tests in IDE or from the command line
  • Extends the JUnit tests framework and has all advantages of the JUnit tests (annotations, asserts, IDE support etc)

The censhare Framework gives the possibility to run tests using a running server.

  • It can be used to test Commands and Asset applications
  • No mockups needed
  • The back-end data models can be tested
  • Easy access to test

Testing commands

To create a test for a command, create a class which extends the AbstractServerLiveTest:

public class MyCommandLiveTest extends AbstractServerLiveTest {
 
     @Test
     public void testMyCommand() throws Exception {
         APICommand command = createCommand( "com.censhare.api.test.command_framework.EchoCommand" );
 
         InputData1 input = new InputData1();
         input.key = "My input data" ;
 
         CommandPromise promise = command.execute(Data.wrapPojo(input));
         InputData1 result = promise.waitForResult().getAs(InputData1. class );
         Assert.assertEquals( "echo command finished with wrong state" , CommandStatus.COMPLETED, promise.getStatus());
         Assert.assertEquals( "echo command returned wrong result" , input.key, result.key);
     }
}

Testing applications

To test an application, create a class which extends the AbstractAssetApplicationLiveTest:

public class MyApplicationLiveTest extends AbstractAssetApplicationLiveTest {
     @Test
     public void testMyMethod() throws Exception {
 
         //opens an application with context asset
         Asset testAsset = queryTestAssetEx( "censhare:myAsset-testKey" );
         env = openApplication(MyApplication. class , testAsset);
         MyApplication application = accessCommandHandler(MyApplication. class , env.applicationCommand);
 
         //call the application method
         MyInputParam inputParam = new MyInputParam();
         inputParam.param1 = "Some data" ;
         inputParam.param2 = "Another data" ;
 
         //call the application method and check the result
         SelfDescribingApplicationData resultData = callMethod(env, "myMethod" , inputParam).getData();
         SelfDescribingApplicationResult result = resultData.result;
         assertNotNull( "Test failed. Did't get a result." , result);
 
         //other checks
         ...
     }
}

Running tests

From the command line:

  • Restart or refresh your local censhare Server
  • Call the ANT target: bin/build.sh junit.live.localhost
  • Builds the sources and runs all live tests against your local censhare server
  • Useful if one wants to check all live tests at once

From within an IDE (Eclipse, IntelliJ):

  • Restart or refresh your local censhare server, make sure to open your JUnit live test class
  • Choose „Run as JUnit test“
  • Executes the test against („within“) your local server
  • Collects and displays the test results

The censhare Live Tests is extended by the JUnit test framework. Therefore, all JUnit test annotations and assert classes can be used.

The Live test framework uses the censhare user with an empty password to execute tests.

Make sure to refresh you server after every test modification to run the latest version of test.

Writing tests

  • Create a java class extending the AbstractServerLiveTest class (for command handlers) or the AbstractAssetApplicationLiveTest class (for applications).
  • Add @Test cases
  • Optionally add @Before, @After cases

Read more about live tests on Command Handlers here.
Read more about live tests on Asset applications here.

Using assets

The Live tests framework provides an API to work on tests with test assets:

Asset queryTestAssetEx(String testKey) - Search for test assets marked with the feature “censhare:test.key” = testKey. It throws an assertion error if the asset is not found or more than one assets are found.

Asset asset = queryTestAssetEx( "censhare:approval.picture.1" );
 
DoubleProperty completionState = asset.properties().getProperty(ResourcePlanningTrait.DEF.taskCompletionPercentage);
Assert.assertNotEquals( "Completion state is already 100" , 100 , completionState.getValueOrDefault( 0.0 ), 0.001 );

Note! All test assets should be available on the system, but they should stay unchanged.

Reset asset changes

There are 2 common ways to reset any asset changes in live tests:

  • Make sure to not to modify any test assets, instead create duplicates and add them to the assetsToDelete object provided by the AbstractServerLiveTest. The assetsToDelete object gets cleaned up after every test:

Asset testAssetTemplate = queryTestAssetEx( "my-asset-key" );
 
  List<Asset> duplicatedAssets = testAssetTemplate.asTemplate().getDuplicator().setHierarchical( true ).duplicate();
for (Asset asset : duplicatedAssets) {
     assetsToDelete.add(asset.getId());
     ...
}

It’s recommended to set the Copy-template feature to any assets which will used for duplication.

  • Add an asset compensation when the asset is modified. The compensation will be executed after the test.

AssetCompensation implementations must expect that their compensate method might be called multiple times, since the entire cleanup/compensation code is executed within an Atomic, which will be retried in case of a failure.

Asset asset = queryTestAssetEx( "censhare:approval.picture.1" );
 
DoubleProperty completionState = asset.properties().getProperty(ResourcePlanningTrait.DEF.taskCompletionPercentage);
Assert.assertNotEquals( "Completion state is already 100" , 100 , completionState.getValueOrDefault( 0.0 ), 0.001 );
 
//adding asset compensation
addAssetCompensation(asset.getSelf(), (a) -> {
     a.properties().setProperty(ResourcePlanningTrait.DEF.taskCompletionPercentage, completionState.getValue());
});
 
InputData inputData = new InputData();
inputData.assetRef = asset.getSelf().toExternalForm();
 
// Test asset handler functionality
APICommand command = createCommand( "com.censhare.api.dam.assetmanagement.completeTask" );
Assert.assertNotNull( "failed to create command" , command);
CommandPromise promise = command.execute(Data.wrapPojo(inputData));
promise.waitForResult();
 
// Refresh the asset and check
asset = queryTestAssetEx( "censhare:approval.picture.1" );
DoubleProperty newCompletionState = asset.properties().getProperty(ResourcePlanningTrait.DEF.taskCompletionPercentage);
Assert.assertEquals( "Completion state is wrong" , 100 , newCompletionState.getValueOrDefault( 0.0 ), 0.001 );

Test assets

Every test asset has to be marked with features:

  • censhare:test - Marker feature for every test asset
  • censhare:test.key - Unique key to identify a test asset (optional). Used by the test to find its particular test asset(s)

Asset names should follow a naming schema:

  • censhare:application-name.asset-type.index/name - e.g. censhare:layouteditor.layout.1

Depending on the way test assets are stored, they can be imported into the system:

  • Via the standard asset import for “*.censhare-assets”
  • Via executing the “Synchronize system module assets” server action for xml asset definitions

Xml asset definitions are easier to maintain, therefore it’s recommended to always prefer xml definitions to “*.censhare-assets”.
Use packages of “*.censhare-assets” only for complex asset structures.

Location for the test assets:

  • censhare-Server/install/assets/optional/censhare5-web/test for “*.censhare-assets”
  • censhare-Server/install/system/optional/web/test for xml definitions

Accessing command context

Use the CommandContext getCommandContext() method to get the command context of a command.
The method returns the context of the command that the test was started from. This method never returns null, since every test is executed via an internal command by the test framework.

The CommandContext can be used to execute Atomic blocks when some asset manipulations are done:

Asset adAsset = queryTestAssetEx( "censhare:flatplan.labels-test.ad.1" );
 
// Change the name of the ad asset
getCommandContext().executeAtomically(() -> {
     UpdatingRepository repo = Platform.getCCServiceEx(UpdatingRepository. class );
     AtomicRef atomicRef = repo.getAsset(adAsset.getSelf());
     Asset asset = atomicRef.get();
     asset.traitDisplay().setName(newAssetName);
     atomicRef.set(asset);
     return null ;
});

The CommandContext can be used when it’s required to execute a test from another user:

getCommandContext().executeAsUser(party.getId(), () -> {
     ...
     return null ;
});

Name conventions

  • The java class for live tests must have a name which ends with „LiveTest“: SampleServerLiveTest, MyApplicationLiveTest
  • All test classes must be located in a sub package named „test“

Command Handlers

The live test class for the command handler should extend class AbstractServerLiveTest.

Execute a command within a live test

There are two methods provided by the AbstractServerLiveTest class to create a command object:

  • APICommand createCommand(String commandName) - A convenience function to create a command. The command is created in the current context if it is a CommandContext. Otherwise method getCommandContext() is used. It can be used by tests, which test certain commands or need a command for other purposes.
  • APICommand createCommand(Class<?> commandHandlerClass) - A convenience function to create a command. It can be used by tests, which test certain commands or need a command for other purposes.

// Call the command to restore a version
APICommand command = createCommand( "com.censhare.api.dam.assetmanagement.restoreVersion" );
AssetHandlers.AssetKeyParam param = new AssetHandlers.AssetKeyParam();
param.assetRef = AssetRef.createVersioned(asset.getId(), 1 ).toExternalForm(); // restore version 1
 
//execute the command
CommandPromise promise = command.execute(Data.wrapPojo(param));
CommandResult commandResult = promise.waitForResult( 20 , TimeUnit.SECONDS);
if (commandResult == null )
     throw new TimeoutException();
assertEquals( "command not completed successfully" , CommandStatus.COMPLETED, commandResult.getStatus());

Application

A live test class for a command handler should extend the AbstractAssetApplicationLiveTest class which extends the AbstractServerLiveTest class.
The AbstractAssetApplicationLiveTest class provides the following additional methods:

  • ApplicationTestEnv openApplication(Class<? extends AbstractAssetApplication> applicationClass, Asset editorAsset) - Initializes and opens an asset application without checking out the given asset
  • ApplicationTestEnv openApplicationAndCheckOut(Class<? extends AbstractAssetApplication> applicationClass, Asset editorAsset) - Initializes and opens an asset application including checking out the given asset
  • void closeApplicationForceAbort(ApplicationTestEnv env) - It should be called after tests are done
  • void initApplicationPriorInitShared(AbstractAssetApplication application - It can be overridden by sub classes to inject some initialization parameters into the application. That can be used for instance to disable or enable certain features, which should or should not be tested. Note that this method is called on a very early phase of initialization (immediately before initShared will be invoked). The default implementation does nothing.
  • SelfDescribingApplicationData callMethod(ApplicationTestEnv env, String methodName, Object param) - A convenience method to execute a method of the application interface. It returns the new view model and available methods of the application.
  • SelfDescribingApplicationData callMethodChecked(ApplicationTestEnv env, String methodName, Object param) - Same as the callMethod(), but fails on any application errors

Opening and closing an application

It’s recommended to add @Before and @After methods to asset application test class, where the application will be initialized and closed:

public class LayoutEditorLiveTest extends AbstractAssetApplicationLiveTest {
     private ApplicationTestEnv env;
     private LayoutEditorTestApplication application;
     private LayoutEditorModel model;
 
     @Before
     private void openApplicationAndCheckOut() throws Exception {
         Asset documentAsset = queryTestAssetEx( "censhare:layouteditor.layout.1" );
 
         env = openApplicationAndCheckOut(LayoutEditorTestApplication. class , documentAsset);
         application = accessCommandHandler(LayoutEditorTestApplication. class , env.applicationCommand);
         model = application.getModel();
     }
 
     @After
     public void afterEachTest() {
         closeApplicationForceAbort(env);
     }
 
     ...
}

Testing application methods

Use the callMethod() or the callMethodChecked() methods to test any application methods.

// add a new tactic to an existing campaign
AddOrMoveBudgetNodeInputParam inputParam = new AddOrMoveBudgetNodeInputParam();
Asset campaignAsset1 = queryTestAssetEx(PARENT_CAMPAIGN_1_KEY);
inputParam.parentAssetRef = campaignAsset1.getSelf().toExternalForm();
inputParam.childAssetRefs = Arrays.asList(newTacticAsset.getSelf().toExternalForm(), newTacticAsset2.getSelf().toExternalForm());
inputParam.move = false ;
 
SelfDescribingApplicationData data = callMethodChecked(env, "addOrMoveBudgetNode" , inputParam);
newViewModel = (BudgetManagementViewModel) data.data;
 
//check if the newViewModel is correct
assert (...);