Exécuter un Drools operation handler en test unitaire

02/10/2018

Introduction

Cet article explique comment exécuter un fichier Drools à partir d’une classe de test, en mockant les classes de service.

Sources

Un projet exemple est disponible dans le projet flower-extras.

Dans ce projet, on teste unitairement un handler basé sur la réponse à une tâche. Les tests unitaires sont situés dans la classe TaskAnswerTest.

Import du fichier Drools

Le code suivant permet de charger le fichier FileCheckAtCreationTest.xls et de le renvoyer lorsque Flower va chercher le fichier Drools.

public DocumentFile buildRulesFile(String filename) throws URISyntaxException
{
	 DocumentFile file = new DocumentFile();
    URL url = ClassLoader.getSystemResource("Drools/" + filename);
    file.setContent(new DataHandler(new FileDataSource(new File(url.toURI()))));
    return file;
}

@Before
public void setUpContext() throws TechnicalException, FunctionalException, URISyntaxException
{
    when(documentService.getFiles(any(Id.class), anyBoolean()))
            .thenReturn(toList(buildRulesFile("FileCheckAtCreationTest.xls")));
}

Mock des services

Les classes de services sont mockées via un objet Java avec l’annotation @Configuration, une méthode avec l’annotation @Bean et en utilisant la méthode mockde Mockito comme ci-dessous :

@Configuration
public class ServiceConfigTest
{
    (...) 
    
    @Bean
    DocumentService documentService()
    {
        return mock(DocumentService.class);
    }

	(...) 
}

Pour les classes non mockées, la procédure est presque identique, on appelle le constructeur dans la méthode :

@Import({ CacheConfiguration.class, InternalServicesConfiguration.class, ServiceConfigTest.class })
protected static class AbstractDroolsHandlerConfig
{
    (...) 
    @Bean
    DroolsOperationHandler droolsOperationHandler()
    {
        return new DroolsOperationHandler();
    }
    
    (...) 
}

Dans le code ci-dessous, les différents mocks sont injectés dans la classe DroolsOperationHandler.

La récupération avec @Autowired de l’objet documentService permet de récupérer l’object mocké.

@Autowired
@InjectMocks
protected DroolsOperationHandler operationHandler;

@Autowired
protected DocumentService documentService;

(...)
Mockito.when(documentService.(...)).thenReturn(...);
(...)

Exécution du handler

Pré-requis

  • Le fichier Drools a été chargé
  • Un contexte a été fourni pour authentification (voir méthode ci-dessous)

    private void setUpCredentials()
    {
    AuthenticatedUser user = new SpringPrincipalUser();
    user.setId(new Id("user"));
    user.setScope(new Id("GEC"));
    SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(user, null));
    }
    
    @Before
    public void setUp() throws TechnicalException, FunctionalException, URISyntaxException
    {
    	MockitoAnnotations.initMocks(this);
    setUpCredentials();
    (...)
    }
    

Spécificité liée au changement de classe


Si le handler entraine un changement de classe de composant, il peut être nécessaire de définir la liste des tags présents sur les classes de composant concernées. Sans quoi les tags ne seront pas conservés après mise à jour de la classe.

Par exemple sur une classe de tâche, si on a une réponse qui :

  • met à jour une propriété Service avec une valeur Service 1
  • met à jour la classe de Tache à traiter à Tâche en traitement

Si la propriété Service n’est pas définie sur la classe de tâche Tâche en traitement, on ne pourra pas tester si la propriété Service a été correctement renseignée.

when(taskClass.getTagReferences()).thenReturn(ComponentBuilder.initTagsReference(Lists.newArrayList("Service"))); 

Construction de l’objet OperationContext

L’exécution du handler prend en entrée un élément de type OperationContext, qui dépend du type de handler testé. Ce contexte est ensuite récupérable via le fichier Drools. L’objet à construire dépend donc fortement du fichier Drools testé.

Ci-dessous quelques exemples de contextes utilisés :

//Update document content
private UpdateContentOperationContext buildComponentContext(String fileName)
{
    UpdateContentOperationContext updateContentContext = new UpdateContentOperationContext();
    updateContentContext.setComponent(ComponentBuilder.buildDocument(fileName));
    IdentifiableElement documentFile = ComponentBuilder.buildFile(fileName);
    List<IdentifiableElement> content = Lists.newArrayList(documentFile);
    updateContentContext.setContent(content);
    updateContentContext.setRegistration(new Id("CONTEXT"));
    return updateContentContext;
}

public TaskOperationContext buildTaskContext(Task task)
{
    List<Component> components = new ArrayList<Component>();
    components.add(task);
    TaskOperationContext taskContext = new TaskOperationContext();
    taskContext.setComponents(components);
    taskContext.setRegistration(new Id("CONTEXT"));
    return taskContext;
}

Appel du handler

Le handler est appelé grâce à la méthode process, qui prend en entrée le contexte créé précédemment.

Dans l’exemple ci-dessous, on simule une création de tâche (réponse Initiate). Le handler récupère la valeur du tag g_destinataire et lui assigne la tâche.

Ci-dessous le test permettant de vérifier si la tâche est bien affectée à l’utilisateur :

@Test
public void should_assign_at_creation_if_receiver_tag_is_filled() throws Exception
{
	Task task = ComponentBuilder.createTask("Initiate");
	task.getTags().getTags().add(TagBuilder.name("g_destinataire").value("USER").build());
	task.setAnswer(ComponentBuilder.answer("Initiate"));
    
    operationHandler.process(buildTaskContext(task));
    
    assertEquals("USER", task.getAssignee());
}