Studio

HOWTO: Unit Test a Studio Bundle

Updated: December 4, 2024

In this how to, you will learn how to unit test a Studio Bundle from a Java Project.

Optional - Creating a Bare Project

If you already have a project you can go to the next section.

  1. Install Nuxeo CLI.
  2. Create a new bare project:

    mkdir my-project && cd $_
    nuxeo bootstrap
    

    Do not hesitate to read the dedicated page to fill in the inputs.

  3. Check that everything is working

    mvn install
    

    And the process must be exited without any errors.

Add Your Connect Credentials to Maven

Adding your credentials allows Maven to be authenticated in Studio while trying to grab your project.

  1. Open ~/.m2/settings.xml file with your preferred text editor
  2. Add the following server node:

    <settings>
    ...
      <servers>
      ...
        <server>
          <id>nuxeo-studio</id>
          <username>your_nos_username</username>
          <password>your_nos_token</password>
        </server>
      </servers>
    </settings>
    

    It is recommended that you don't leave your token directly in the XML file. You can encrypt it by following the official password encryption guide.

related documentation
Check our token management page to see how to create a token.

Adding Nuxeo Studio as Maven Repository

  1. Open ./pom.xml file with your preferred editor.
  2. Check if the following repository is present. If not, add it:

    <project>
    ...
      <repositories>
      ...
        <repository>
          <id>nuxeo-studio</id>
          <url>https://connect.nuxeo.com/nuxeo/site/studio/maven</url>;
          <releases>
            <enabled>true</enabled>
          </releases>
          <snapshots>
            <updatePolicy>always</updatePolicy>
            <enabled>true</enabled>
          </snapshots>
        </repository>
      </repositories>
    </project>
    

Adding Studio Bundle Dependency

  1. Open Nuxeo Studio and the project that you want to unit test.
  2. Pick your Maven GAV information in Studio. Go to Settings > Application Information.

    • groupId: is the Maven Group field
    • artifactId: is the Maven Artifact Id field
    • version: follows this convention X.Y.W--branch-SNAPSHOT.

    For example, if you are working on version 0.0.2-SNAPSHOT (open Source Control > Branch Management) and on branch master, the Maven version will be: 0.0.2--master-SNAPSHOT. If you want a released version, the version pattern is much easier: 0.0.2.

  3. Then edit the ./pom.xml file to add the dependency management. It's recommended to set the dependency's version in the parent pom of your project. For more information, you can read Dependency Management in the official Maven guide.

    <project>
    ...
      <dependencyManagement>
      ...
        <dependencies>
          <dependency>
            <groupId>STUDIO_GROUPID</groupId>
            <artifactId>STUDIO_ARTIFACTID</artifactId>
            <version>STUDIO_VERISON</version>
          </dependency>
        </dependencies>
      </dependencyManagement>
    </project>
    
  4. Add a direct dependency in the ./myproject-core/pom.xml file, or the pom.xml of your module in which you'll want to write the unit tests.

    <project>
    ...
      <dependencies>
        <!-- Note that we do not need the version here, it's inherited from the parent pom.xml file. -->
        <dependency>
          <groupId>STUDIO_GROUPID</groupId>
          <artifactId>STUDIO_ARTIFACTID</artifactId>
        </dependency>
      </dependencies>
    </project>
    

Understanding @PartialDeploy Annotation

Once you've understood the @Deploy annotation, you will have noticed that deploying a Studio Bundle is not as straightforward as it sounds because of the way the Studio bundle contributions are packed. The idea behind this annotation is to allow you to select which contributions you want to deploy in your unit test.

For example, you may want to deploy only Schema, Document Type and custom event listeners to make sure that your business rules are correctly applied. Or, you may simply want to verify that your Automation Scripting is doing what you'd expect.

Testing Content Model

  1. Assuming you have already created a DocType, "MyCustomDoc", in your Studio Project...
  2. Add the following dependency to ./myproject/pom.xml file:

    <dependencies>
    ...
      <dependency>
          <groupId>org.nuxeo.runtime</groupId>
          <artifactId>nuxeo-runtime-test</artifactId>
          <scope>test</scope>
      </dependency>
      <dependency>
          <groupId>org.nuxeo.ecm.platform</groupId>
          <artifactId>nuxeo-platform-test</artifactId>
          <scope>test</scope>
      </dependency>
    <dependencies>
    
  3. Create a new test case class ./myproject-core/src/test/java/com/bigcorp/MyDocType.java

  4. Add the following content:

    package com.bigcorp;
    
    import static org.junit.Assert.assertNotNull;
    
    import javax.inject.Inject;
    
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.nuxeo.ecm.core.api.CoreSession;
    import org.nuxeo.ecm.core.api.DocumentModel;
    import org.nuxeo.ecm.core.test.DefaultRepositoryInit;
    import org.nuxeo.ecm.core.test.annotations.RepositoryConfig;
    import org.nuxeo.ecm.platform.test.PlatformFeature;
    import org.nuxeo.runtime.test.runner.Features;
    import org.nuxeo.runtime.test.runner.FeaturesRunner;
    import org.nuxeo.runtime.test.runner.PartialDeploy;
    import org.nuxeo.runtime.test.runner.TargetExtensions;
    
    @RunWith(FeaturesRunner.class)
    @Features(PlatformFeature.class)
    @RepositoryConfig(init = DefaultRepositoryInit.class)
    // Note that the dedicated Annotation takes the bundle name and the whitelisted target extensions list
    @PartialDeploy(bundle = "studio.extensions.YOUR_PROJECT_NAME", extensions = { TargetExtensions.ContentModel.class })
    public class MyDocTypeTest {
    
      @Inject
      CoreSession session;
    
      @Test
      public void testMyDocumentType() {
         DocumentModel contract = session.createDocumentModel("/default-domain/Workspaces",
                                                              "firstContract", "Contract");
         contract.setPropertyValue("contract:amount", 200L);
         DocumentModel document = session.createDocument(contract);
    
         assertNotNull(document.getId());
      }
    }
    
  5. Make sure that you change the YOUR_PROJECT_NAME to the correct one.
  6. And voila! The test should pass and you have only deployed what is necessary to test the Content Model category in Studio.

You can find your YOUR_PROJECT_NAME opening your studio project:

/META-INF
  MANIFEST.MF
    Bundle-SymbolicName:studio.extensions.XXXX

Testing Automation

  1. Assuming that you have already created an Automation Chain, UpdateTaxes, that calculates contract:taxes from contract:amount...
  2. Add the following dependency to ./myproject/pom.xml file:
    <dependencies>
    ...
      <dependency>
        <groupId>org.nuxeo.ecm.automation</groupId>
        <artifactId>nuxeo-automation-test</artifactId>
        <scope>test</scope>
      </dependency>
    <dependencies>
    
  3. Create a new test case class ./myproject-core/src/test/java/com/bigcorp/UpdateTaxesTest.java
  4. Add the following content:

       package com.bigcorp;
    
       import static org.junit.Assert.assertEquals;
    
       import javax.inject.Inject;
    
       import org.junit.Test;
       import org.junit.runner.RunWith;
       import org.nuxeo.ecm.automation.AutomationService;
       import org.nuxeo.ecm.automation.OperationContext;
       import org.nuxeo.ecm.automation.OperationException;
       import org.nuxeo.ecm.automation.test.AutomationFeature;
       import org.nuxeo.ecm.core.api.CoreSession;
       import org.nuxeo.ecm.core.api.DocumentModel;
       import org.nuxeo.runtime.test.runner.Features;
       import org.nuxeo.runtime.test.runner.FeaturesRunner;
       import org.nuxeo.runtime.test.runner.PartialDeploy;
       import org.nuxeo.runtime.test.runner.TargetExtensions;
    
       @RunWith(FeaturesRunner.class)
       @Features({ AutomationFeature.class })
       // In the case of Automation, we whitelisted all the Automation extensions (which also contains Content Model)
       @PartialDeploy(bundle = "studio.extensions.YOUR_PROJECT_NAME", extensions = { TargetExtensions.Automation.class })
       public class UpdateTaxesTest {
    
           @Inject
           AutomationService automationService;
    
           @Inject
           CoreSession session;
    
           @Test
           public void taxesCalculationTest() throws OperationException {
               DocumentModel doc = session.createDocumentModel("/", "test-file", "Contrat");
               doc.setPropertyValue("contract:amount", 200);
               doc = session.createDocument(doc);
    
               OperationContext ctx = new OperationContext(session);
               ctx.setInput(doc);
    
               doc = (DocumentModel) automationService.run(ctx, "UpdateTaxes");
               assertEquals(11, doc.getPropertyValue("contract:taxes"));
           }
       }
    
  5. Verify that you've changed YOUR_PROJECT_NAME to the correct one.

Testing Automation Scripting

  1. Assuming that you have already created an Automation Script, SumTaxes, that returns the sum of all taxes...
  2. Create a new test case class ./myproject-core/src/test/java/com/bigcorp/SumTaxes.java
  3. Add the following content:

       package com.bigcorp;
    
       import static org.junit.Assert.assertEquals;
    
       import java.util.stream.IntStream;
    
       import javax.inject.Inject;
    
       import org.junit.Test;
       import org.junit.runner.RunWith;
       import org.nuxeo.ecm.automation.AutomationService;
       import org.nuxeo.ecm.automation.OperationContext;
       import org.nuxeo.ecm.automation.OperationException;
       import org.nuxeo.ecm.automation.test.AutomationFeature;
       import org.nuxeo.ecm.core.api.CoreSession;
       import org.nuxeo.ecm.core.api.DocumentModel;
       import org.nuxeo.runtime.test.runner.Features;
       import org.nuxeo.runtime.test.runner.FeaturesRunner;
       import org.nuxeo.runtime.test.runner.PartialDeploy;
       import org.nuxeo.runtime.test.runner.TargetExtensions;
    
       @RunWith(FeaturesRunner.class)
       @Features({ AutomationFeature.class })
       @PartialDeploy(bundle = "studio.extensions.YOUR_PROJECT_NAME", extensions = { TargetExtensions.Automation.class })
       public class SumTaxes {
    
           @Inject
           AutomationService automationService;
    
           @Inject
           CoreSession session;
    
           private final int TAXES = 10;
    
           @Test
           public void sumTaxesTest() throws OperationException {
               final int NB_DOCS = 10;
               IntStream.range(0, NB_DOCS).forEach(this::createDocument);
               OperationContext ctx = new OperationContext(session);
    
               // Note that the Scripting name is prefixed by "javascript.".
               int taxes = (int) automationService.run(ctx, "javascript.SumTaxes");
               assertEquals(NB_DOCS * TAXES, taxes);
           }
    
           protected void createDocument(int index) {
               DocumentModel doc = session.createDocumentModel("/", "test-file-" + index, "Contrat");
               doc.setPropertyValue("contract:taxes", TAXES);
               session.createDocument(doc);
           }
       }
    
  4. Verify that you've changed YOUR_PROJECT_NAME to the correct one.

Writing Your Own TargetExtensions Class

If you need to partially deploy other contributions to a specific target, it's possible to create your own TargetExtension class.

  1. Create a new class ./myproject-core/src/test/java/com/bigcorp/BigCorpExtension.java
  2. Add the following content:

       package com.bigcorp;
    
       import org.nuxeo.runtime.test.runner.TargetExtensions;
    
       public class BigCorpExtension extends TargetExtensions {
           @Override
           protected void initialize() {
               // Add whitelisted targets
               addTargetExtension("com.bigcorp.component", "target");
               addTargetExtension("com.bigcorp.component", "target-2");
           }
       }
    
  3. Add it to the list of extensions in the expected @PartialDeploy annotation like:
       @PartialDeploy(bundle = "studio.extensions.YOUR_PROJECT_NAME", extensions = { BigCorpExtension.class, TargetExtensions.Automation.class })
    
  4. And that's it!