Komponenty najlepiej testować z frameworkami TestNG oraz Mockito, gdyż istnieją one już w projekcie systemu. TestNG służy do tworzenia kolejnych testów jednostkowych, sprawdzania warunków, inicjalizacji testów, łapania pożądanych wyjątków itp. Mockito służy natomiast do tworzenia implementacji i wstrzykiwania obiektów, które są niezbędne do przetestowania niektórych funkcjonalności (np. gdy komponent używa systemowych serwisów ProcessService, UserService itp.).

Przykładowy datachooser korzystający z serwisu UserFinder:

@DataChooser
public class FindingUserDataChooser
{
    @Autowired
    private UserFinder userFinder;

    @Define
    public void definition( DataChooserDefinitionBuilder builder )
    {
        // @formatter:off
        builder
            .id( "finding-user-datachooser" )
            .name( "finding-user-datachooser-name" )
            .description( "finding-user-datachooser-desc" )
            .category( Categories.CUSTOM )
                .mapping()
                    .id( "fullname-mapping" )
                    .name( "fullname-mapping-name" )
                    .description( "fullname-mapping-desc" )
                    .create();
        // @formatter:on
    }

    public void data( ComponentQueryData queryData, DataChooserResult result )
    {
        String username = queryData.getQuery();

        DetachedCriteria criteria = DetachedCriteria.forClass( User.class );
        criteria.add( Restrictions.like( "userName", username ) );
        List<User> users = userFinder.findByCriteria( criteria );
        // itd...
    }
}

Do tak stworzonego komponentu zostanie wstrzyknięty przez adnotację @Autowired systemowy serwis UserFinder.

Aby używać systemowe serwisy we wtyczkach, należy zaimportować je również w deskryptorze wtyczki (Import i udostępnianie serwisów).

Przykładowy test powyższej klasy miałby postać:

@Test
public class FindingUserDataChooserTest
{
    @InjectMocks
    FindingUserDataChooser component = new FindingUserDataChooser();

    @Mock
    UserFinder userFinder;

    @Before
    private void init()
    {
        MockitoAnnotations.initMocks( this );
        when( userFinder.findByCriteria( any( DetachedCriteria.class ) ) ).thenReturn( getSampleUserList() );
    }

    private List<User> getSampleUserList()
    {
        User user1 = new User( "user1", "password" );
        User user2 = new User( "user2", "password" );
        return Lists.newArrayList( user1, user2 );
    }

    @Test
    private void componentTest()
    {
        // ...
    }
}

Serwis UserFinder został zmockowany przez adnotację @Mock. Statyczne metody when i thenReturn określają zachowanie się serwisu UserFinder dla konkretnych wywołań. Należy pamiętać, że nie jest to prawdziwy systemowy serwis, a jedynie tymczasowa implementacja na czas wykonywania testów. Adnotacja @InjectMocks mówi o tym, iż do stworzonego obiektu komponentu (FindingUserDataChooser) Mockito będzie próbował wstrzyknąć implementacje do jego pól (w tym przypadku wstrzyknie UserFindera).

Systemowe serwisy można również wstrzykiwać w komponentach przez stworzenie oznaczonego adnotacją @Autowired konstruktora posiadającego w parametrach kolejne serwisy. Przykładowy komponent:

@DataChooser
public class FindingUserDataChooser
{
    private UserFinder userFinder;

    @Autowired
    public FindingUserDataChooser( UserFinder userFinder )
    {
        this.userFinder = userFinder;
    }

    @Define
    public void definition( DataChooserDefinitionBuilder builder )
    {
        // @formatter:off
        builder
            .id( "finding-user-datachooser" )
            .name( "finding-user-datachooser-name" )
            .description( "finding-user-datachooser-desc" )
            .category( Categories.CUSTOM )
                .mapping()
                    .id( "fullname-mapping" )
                    .name( "fullname-mapping-name" )
                    .description( "fullname-mapping-desc" )
                    .create();
        // @formatter:on
    }

    public void data( ComponentQueryData queryData, DataChooserResult result )
    {
        String username = queryData.getQuery();
        DetachedCriteria criteria = DetachedCriteria.forClass( User.class );
        criteria.add( Restrictions.like( "userName", username ) );
        List<User> users = userFinder.findByCriteria( criteria );
        // itd.
    }
}

W takim przypadku również serwis UserFinder zostanie poprawnie zaciągnięty przez komponent. Komponent można przetestować w poniższy sposób:

@Test
public class FindingUserDataChooserTest
{
    FindingUserDataChooser component;

    @Mock
    UserFinder userFinder;

    @Before
    private void init()
    {
        MockitoAnnotations.initMocks( this );
        when( userFinder.findByCriteria( any( DetachedCriteria.class ) ) ).thenReturn( getSampleUserList() );
        component = new FindingUserDataChooser( userFinder );
    }

    private List<User> getSampleUserList()
    {
        User user1 = new User( "user1", "password" );
        User user2 = new User( "user2", "password" );
        return Lists.newArrayList( user1, user2 );
    }

    @Test
    private void componentTest()
    {
        // ...
    }
}

Pamiętać należy również, że do wywołania komponentów należy stworzyć specyficzne dla rodzaju komponentu obiekty. Przykładowo dla powyższego DataChoosera można zauważyć, iż metoda data przyjmuje obiekty ComponentQueryData oraz DataChooserResult, które w testach należy własnoręcznie stworzyć lub zmockować.

Components are best tested with TestNG and Mockito frameworks, as they already exist in the system design. TestNG is used to create more unit tests, check conditions, initialize tests, catch desired exceptions, etc. Mockito, on the other hand, is used to create implementations and inject objects that are necessary to test some functionality (e.g. when a component uses system ProcessService, UserService, etc.).

Example datachooser using UserFinder service:

@DataChooser
public class FindingUserDataChooser
{
    @Autowired
    private UserFinder userFinder;

    @Define
    public void definition( DataChooserDefinitionBuilder builder )
    {
        // @formatter:off
        builder
            .id( "finding-user-datachooser" )
            .name( "finding-user-datachooser-name" )
            .description( "finding-user-datachooser-desc" )
            .category( Categories.CUSTOM )
                .mapping()
                    .id( "fullname-mapping" )
                    .name( "fullname-mapping-name" )
                    .description( "fullname-mapping-desc" )
                    .create();
        // @formatter:on
    }

    public void data( ComponentQueryData queryData, DataChooserResult result )
    {
        String username = queryData.getQuery();

        DetachedCriteria criteria = DetachedCriteria.forClass( User.class );
        criteria.add( Restrictions.like( "userName", username ) );
        List<User> users = userFinder.findByCriteria( criteria );
        // itd...
    }
}

The system UserFinder service will be injected into the component created in this way by the @Autowired annotation.

To use system services in plugins, you must also import them in the plugin descriptor (Import and share services).

An example test of the above class would be:

@Test
public class FindingUserDataChooserTest
{
    @InjectMocks
    FindingUserDataChooser component = new FindingUserDataChooser();

    @Mock
    UserFinder userFinder;

    @Before
    private void init()
    {
        MockitoAnnotations.initMocks( this );
        when( userFinder.findByCriteria( any( DetachedCriteria.class ) ) ).thenReturn( getSampleUserList() );
    }

    private List<User> getSampleUserList()
    {
        User user1 = new User( "user1", "password" );
        User user2 = new User( "user2", "password" );
        return Lists.newArrayList( user1, user2 );
    }

    @Test
    private void componentTest()
    {
        // ...
    }
}

The UserFinder service has been dockerized by the @Mock annotation.

The static methods when and thenReturn determine the behavior of the UserFinder service for specific calls. Note that this is not a real system service, but only a temporary implementation for the duration of the tests. The @InjectMocks annotation says that to the created component object (FindingUserDataChooser), Mockito will try to inject implementations into its fields (in this case, it will inject UserFinder).

System services can also be injected into components by creating an annotated @Autowired constructor that has further services in its parameters. Example component:

@DataChooser
public class FindingUserDataChooser
{
    private UserFinder userFinder;

    @Autowired
    public FindingUserDataChooser( UserFinder userFinder )
    {
        this.userFinder = userFinder;
    }

    @Define
    public void definition( DataChooserDefinitionBuilder builder )
    {
        // @formatter:off
        builder
            .id( "finding-user-datachooser" )
            .name( "finding-user-datachooser-name" )
            .description( "finding-user-datachooser-desc" )
            .category( Categories.CUSTOM )
                .mapping()
                    .id( "fullname-mapping" )
                    .name( "fullname-mapping-name" )
                    .description( "fullname-mapping-desc" )
                    .create();
        // @formatter:on
    }

    public void data( ComponentQueryData queryData, DataChooserResult result )
    {
        String username = queryData.getQuery();
        DetachedCriteria criteria = DetachedCriteria.forClass( User.class );
        criteria.add( Restrictions.like( "userName", username ) );
        List<User> users = userFinder.findByCriteria( criteria );
        // itd.
    }
}

In this case, the UserFinder service will also be correctly pulled by the component. You can test the component in the following way:

@Test
public class FindingUserDataChooserTest
{
    FindingUserDataChooser component;

    @Mock
    UserFinder userFinder;

    @Before
    private void init()
    {
        MockitoAnnotations.initMocks( this );
        when( userFinder.findByCriteria( any( DetachedCriteria.class ) ) ).thenReturn( getSampleUserList() );
        component = new FindingUserDataChooser( userFinder );
    }

    private List<User> getSampleUserList()
    {
        User user1 = new User( "user1", "password" );
        User user2 = new User( "user2", "password" );
        return Lists.newArrayList( user1, user2 );
    }

    @Test
    private void componentTest()
    {
        // ...
    }
}

Remember also that component type-specific objects must be created for component calls. For example, for the above DataChooser, you can see that the data method accepts ComponentQueryData and DataChooserResult objects, which you need to create or mothball yourself in testing.