component-runtime-junit
component-runtime-junit is a small test library allowing you to validate simple logic based on Talend Component tooling.
To import it add to your project the following dependency:
<dependency>
<groupId>org.talend.sdk.component</groupId>
<artifactId>component-runtime-junit</artifactId>
<version>${talend-component.version}</version>
<scope>test</scope>
</dependency>
This dependency also provide some mocked components that you can use with your own component to create tests.
The mocked components are provided under the family test :
-
emitter: a mock of an input component -
collector: a mock of an output component
JUnit 4
Then you can define a standard JUnit test and use the SimpleComponentRule rule:
public class MyComponentTest {
@Rule (1)
public final SimpleComponentRule components = new SimpleComponentRule("org.talend.sdk.component.mycomponent.");
@Test
public void produce() {
Job.components() (2)
.component("mycomponent","yourcomponentfamily://yourcomponent?"+createComponentConfig())
.component("collector", "test://collector")
.connections()
.from("mycomponent").to("collector")
.build()
.run();
final List<MyRecord> records = components.getCollectedData(MyRecord.class); (3)
doAssertRecords(records); // depending your test
}
}
| 1 | the rule will create a component manager and provide two mock components: an emitter and a collector. Don’t forget to set the root package of your component to enable it. |
| 2 | you define any chain you want to test, it generally uses the mock as source or collector |
| 3 | you validate your component behavior, for a source you can assert the right records were emitted in the mock collect |
JUnit 5
The JUnit 5 integration is mainly the same as for JUnit 4 except it uses the new JUnit 5 extension mecanism.
The entry point is the @WithComponents annotation you put on your test class which takes the
component package you want to test and you can use @Injected to inject in a test class field an instance of ComponentsHandler
which exposes the same utilities than the JUnit 4 rule:
@WithComponents("org.talend.sdk.component.junit.component") (1)
public class ComponentExtensionTest {
@Injected (2)
private ComponentsHandler handler;
@Test
public void manualMapper() {
final Mapper mapper = handler.createMapper(Source.class, new Source.Config() {
{
values = asList("a", "b");
}
});
assertFalse(mapper.isStream());
final Input input = mapper.create();
assertEquals("a", input.next());
assertEquals("b", input.next());
assertNull(input.next());
}
}
| 1 | The annotation defines which components to register in the test context. |
| 2 | The field allows to get the handler to be able to orchestrate the tests. |
if it is the first time you use JUnit 5, don’t forget the imports changed and you must use org.junit.jupiter.api.Test instead of org.junit.Test.
Some IDE versions and surefire versions can also need you to install either a plugin or a specific configuration.
|
Mocking the output
Using the component "test"/"collector" as in previous sample stores all records emitted by the chain (typically your source)
in memory, you can then access them using theSimpleComponentRule.getCollectoedRecord(type). Note that this method filters by type,
if you don’t care of the type just use Object.class.
Mocking the input
The input mocking is symmetric to the output but here you provide the data you want to inject:
public class MyComponentTest {
@Rule
public final SimpleComponentRule components = new SimpleComponentRule("org.talend.sdk.component.mycomponent.");
@Test
public void produce() {
components.setInputData(asList(createData(), createData(), createData())); (1)
Job.components() (2)
.component("emitter","test://emitter")
.component("out", "yourcomponentfamily://myoutput?"+createComponentConfig())
.connections()
.from("emitter").to("out")
.build
.run();
assertMyOutputProcessedTheInputData();
}
}
| 1 | using setInputData you prepare the execution(s) to have a fake input when using "test"/"emitter" component. |
Creating runtime configuration from component configuration
The component configuration is a POJO (using @Option on fields) and the runtime configuration (ExecutionChainBuilder) uses
a Map<String, String>. To make the conversion easier, the JUnit integration provides a SimpleFactory.configurationByExample utility
to get this map instance from a configuration instance.
Example:
final MyComponentConfig componentConfig = new MyComponentConfig();
componentConfig.setUser("....");
// .. other inits
final Map<String, String> configuration = configurationByExample(componentConfig);
The same factory provides a fluent DSL to create configuration calling configurationByExample without any parameter.
The advantage is to be able to convert an object as a Map<String, String> as seen previously or as a query string
to use it with the Job DSL:
final String uri = "family://component?" +
configurationByExample().forInstance(componentConfig).configured().toQueryString();
It handles the encoding of the URI to ensure it is correctly done.
Testing a Mapper
The SimpleComponentRule also allows to test a mapper unitarly, you can get an instance from a configuration
and you can execute this instance to collect the output. Here is a snippet doing that:
public class MapperTest {
@ClassRule
public static final SimpleComponentRule COMPONENT_FACTORY = new SimpleComponentRule(
"org.company.talend.component");
@Test
public void mapper() {
final Mapper mapper = COMPONENT_FACTORY.createMapper(MyMapper.class, new Source.Config() {{
values = asList("a", "b");
}});
assertEquals(asList("a", "b"), COMPONENT_FACTORY.collectAsList(String.class, mapper));
}
}
Testing a Processor
As for the mapper a processor is testable unitary. The case is a bit more complex since you can have multiple inputs and outputs:
public class ProcessorTest {
@ClassRule
public static final SimpleComponentRule COMPONENT_FACTORY = new SimpleComponentRule(
"org.company.talend.component");
@Test
public void processor() {
final Processor processor = COMPONENT_FACTORY.createProcessor(Transform.class, null);
final SimpleComponentRule.Outputs outputs = COMPONENT_FACTORY.collect(processor,
new JoinInputFactory().withInput("__default__", asList(new Transform.Record("a"), new Transform.Record("bb")))
.withInput("second", asList(new Transform.Record("1"), new Transform.Record("2")))
);
assertEquals(2, outputs.size());
assertEquals(asList(2, 3), outputs.get(Integer.class, "size"));
assertEquals(asList("a1", "bb2"), outputs.get(String.class, "value"));
}
}
Here again the rule allows you to instantiate a Processor from your code
and then to collect the output from the inputs you pass in. There are two convenient implementation
of the input factory:
-
MainInputFactoryfor processors using only the default input. -
JoinInputfactoryfor processors using multiple inputs have a methodwithInput(branch, data)The first arg is the branch name and the second arg is the data used by the branch.
you can also implement your own input representation if needed implementing org.talend.sdk.component.junit.ControllableInputFactory.
|