Cucumber-jvm: Allow multiple TypeRegistryConfigurers

Created on 2 Aug 2018  路  10Comments  路  Source: cucumber/cucumber-jvm

From Slack:

Let's say we have three testing projects - two are specific for some platforms (e.g. web/mobile), and third one, that is dependency for both of them, have some common Cucumber steps.
According to documentation, to register ParameterTypes in cucumber-jvm v3 I should add TypeRegistryConfigurer in glue path, but I need different types to be registered in different projects. So when I have some common TypeRegistryConfigurer implementation in root project, I can't add more TypeRegistryConfigurer implementations in dependent projects (because of error, that says we can't add more than one instance of TypeRegistryConfigurer).

I would appreciate a PR that seperates the two methods of the interface. Only the locale method can't be repeated.

pinned enhancement

Most helpful comment

Hi, idea is great, especially for my project. Until it will be implemented I have a workaround for that.

  1. Add interface to your project for the registering in TypeRegistry your DataTableType's and ParameterType's in multiple places/modules but without Locale:
import cucumber.api.TypeRegistry;

public interface MultipleTypeConfigurer {
    void configureTypeRegistry(TypeRegistry registry);
}
  1. Implement above interface whatever times you need.
  2. Add below implementation of TypeRegistryConfigurer in your glue code (do not forget to change implementation of locale() method to return Locale of your Data Tables which is used to determine NumberFormat):
import cucumber.api.TypeRegistry;
import cucumber.api.TypeRegistryConfigurer;
import cucumber.runtime.ClassFinder;
import cucumber.runtime.Reflections;
import cucumber.runtime.io.MultiLoader;
import cucumber.runtime.io.ResourceLoader;
import cucumber.runtime.io.ResourceLoaderClassFinder;
import java.util.Collection;
import java.util.Collections;
import java.util.Locale;

public class TypeConfigureRegistry implements TypeRegistryConfigurer {

    @Override
    public Locale locale() {
        return Locale.ENGLISH;
    }

    @Override
    public void configureTypeRegistry(TypeRegistry typeRegistry) {
        ClassLoader classLoader = this.getClass().getClassLoader();
        ResourceLoader resourceLoader = new MultiLoader(classLoader);
        ClassFinder classFinder = new ResourceLoaderClassFinder(resourceLoader, classLoader);
        Reflections reflections = new Reflections(classFinder);
        Collection<? extends MultipleTypeConfigurer> typeConfigurers = reflections.instantiateSubclasses(
            MultipleTypeConfigurer.class,
            Collections.singletonList("test.glue.package.name"),
            new Class[]{},
            new Object[]{}
        );

        for(MultipleTypeConfigurer configurer : typeConfigurers){
            configurer.configureTypeRegistry(typeRegistry);
        }
    }
}

All 10 comments

Additional Context & Motivation

Lovely idea! Was trying to separate configurers by transformer type like this:

multiple_typeconfigs

~~~
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running jcucumberng.project.RunCukesTest
[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.187 s <<< FAILURE! - in jcucumberng.project.RunCukesTest
[ERROR] initializationError(jcucumberng.project.RunCukesTest) Time elapsed: 0.078 s <<< ERROR!
cucumber.runtime.TooManyInstancesException: Expected only one instance, but found too many: [jcucumberng.project.datatable.TableEntryConfigurer@42d3bd8b, jcucumberng.project.datatable.TableConfigurer@dfd3711]

[INFO]
[INFO] Results:
[INFO]
[ERROR] Errors:
[ERROR] RunCukesTest.initializationError 禄 TooManyInstances Expected only one instance...
[INFO]
[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 10.552 s
[INFO] Finished at: 2018-08-26T09:33:44+08:00
[INFO] ------------------------------------------------------------------------
~~~

Hi, idea is great, especially for my project. Until it will be implemented I have a workaround for that.

  1. Add interface to your project for the registering in TypeRegistry your DataTableType's and ParameterType's in multiple places/modules but without Locale:
import cucumber.api.TypeRegistry;

public interface MultipleTypeConfigurer {
    void configureTypeRegistry(TypeRegistry registry);
}
  1. Implement above interface whatever times you need.
  2. Add below implementation of TypeRegistryConfigurer in your glue code (do not forget to change implementation of locale() method to return Locale of your Data Tables which is used to determine NumberFormat):
import cucumber.api.TypeRegistry;
import cucumber.api.TypeRegistryConfigurer;
import cucumber.runtime.ClassFinder;
import cucumber.runtime.Reflections;
import cucumber.runtime.io.MultiLoader;
import cucumber.runtime.io.ResourceLoader;
import cucumber.runtime.io.ResourceLoaderClassFinder;
import java.util.Collection;
import java.util.Collections;
import java.util.Locale;

public class TypeConfigureRegistry implements TypeRegistryConfigurer {

    @Override
    public Locale locale() {
        return Locale.ENGLISH;
    }

    @Override
    public void configureTypeRegistry(TypeRegistry typeRegistry) {
        ClassLoader classLoader = this.getClass().getClassLoader();
        ResourceLoader resourceLoader = new MultiLoader(classLoader);
        ClassFinder classFinder = new ResourceLoaderClassFinder(resourceLoader, classLoader);
        Reflections reflections = new Reflections(classFinder);
        Collection<? extends MultipleTypeConfigurer> typeConfigurers = reflections.instantiateSubclasses(
            MultipleTypeConfigurer.class,
            Collections.singletonList("test.glue.package.name"),
            new Class[]{},
            new Object[]{}
        );

        for(MultipleTypeConfigurer configurer : typeConfigurers){
            configurer.configureTypeRegistry(typeRegistry);
        }
    }
}

This issue has been automatically marked as stale because it has not had recent activity. It will be closed in a week if no further activity occurs.

Hi @mpkorstanje , can this be implemented on Cucumber3 and Cucumber4?

Yes. I would think it is possible to implement this in v4. But I personally don't have a need nor a desire to do this at the moment. If you or anybody else is willing to do the work I can provide some points as to the general approach and where to start.

@mpkorstanje Can you leave the general pointers here so anyone interested can pick it up?

I'd love to but I'm currently a bit short on bandwidth.

This would be a nice to have feature. The work-around solution of @andrii-kolesnyk doesn't work anymore with cucumber 4.3.0.

Before version 4.2.1 the packagePath was enriched with a "classpath:" prefix in the ResourceLoaderClassFinder.

String packagePath = "classpath:" + packageName.replace('.', '/').replace(File.separatorChar, '/');

After version 4.2.1 this was removed. So for the work-around to work you'd have to do this yourself:

Collection<? extends MultipleTypeConfigurer> typeConfigurers = reflections.instantiateSubclasses(
                MultipleTypeConfigurer.class,
                Collections.singletonList(URI.create("classpath:your/package/here/")),
                new Class[] {},
                new Object[] {});

Rather then try to solve the problems of combining multiple TypeRegistryConfigurer with different locales I'm going to consider this issue resolved by https://github.com/cucumber/cucumber-jvm/pull/1677. Using @ParameterType decouples the registration of parameters from TypeRegistryConfigurer all together.

Was this page helpful?
0 / 5 - 0 ratings