Hot questions for Using Cucumber in dependency injection

Top Java Programmings / Cucumber / dependency injection

Question:

What I want: I want to use a spring @Autowired annotation in the file conventionally named "TypeRegistryConfiguration". It works perfectly well for steps file, but for some reason the dependency injection does not work in this file (there is no error/warn message even in debug level). Spring scans "com.funky.steps", which contains the steps, the context and the type registry configuration file, see example below.

Context:

package com.funky.steps.context;

@Component
public class CommonContext {

...

Type registry configuration:

package com.funky.steps.typeregistry;

public class TypeRegistryConfiguration implements TypeRegistryConfigurer {

    @Autowired 
    private CommonContext context; // NOT INJECTED !

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

    @Override
    public void configureTypeRegistry(TypeRegistry typeRegistry) {
        registerStuff(typeRegistry)
    }

    ...

Steps:

package com.funky.steps;

public class WebServiceSteps {

    @Autowired
    private CommonContext context; // Correctly injected

    ...

Why I want it: I have steps that save variables in the context for later use. When I build an object using type registry, I want to be able to access these variables. Example:

Given I call the web service 1
And the response field "id" will be used as "$id" # id is saved in the context
When I call the web service 2: # call type registry configuration to build the request using $id (which I can not access because it is in the context and @Autowired is not working)
| id | $id |
Then ...

Answer:

This is not possible in Cucumber 4.x but you are able to register parameter and data table types as part of the glue in Cucumber 5.0.0-RC1.

Instead of registering a parameter type with registry.registerParameterType you'd use @ParameterType instead. The same works for the data table types.

private final Catalog catalog; 
private final Basket basket;

@ParameterType("[a-z ]+")
public Catalog catalog(String name) {
  return catalogs.findCatalogByName(name);
}

@ParameterType("[a-z ]+")
public Product product(String name) {
  return catalog.findProductByName(name);
}

@Given("the {catalog} catalog")
public void the_catalog(Catalog catalog){
  this.catalog = catalog
}

@When("a user places the {product} in his basket")
public void a_user_place_the_product_in_his_basket(Product product){
  basket.add(product);
}

Note: The method name is used as the parameter name. A parameter name can also be provided via the name property of @ParameterType.

See: https://cucumber.io/blog/announcing-cucumber-jvm-v5-0-0-rc1/

Question:

I'm currently having some issues with implementing object injections properly. I am writing some Cucumber tests with Page Objects Model. I am getting NullPointerExceptions in the following code:

public class BasePage extends LoadableComponent<BasePage>{
    @Inject
    protected WebDriver driver  //trying to inject a WebDriver instance here

    public BasePage(){
        PageFactory.initElements(this.driver, this);    
    }
    @Override
    protected void isLoaded() throws Error {//something here}
    @Override
    protected void load() {//something as well}

    // some more shared methods   
}

Above is my BasePage class, LoginPage extends BasePage as below

public class LoginPage extends BasePage {
    @FindBy(id="login_username")
    private WebElement userNameField;
    @FindBy(id="login_password")
    private WebElement passWordField;
    @FindBy(id="login_database")
    private WebElement dataBase;

    public void enterValidLoginCredentials() throws FileNotFoundException {
        userNameField.sendKeys(EdgeTestProperties.getProperty("userName"));
        passWordField.sendKeys(EdgeTestProperties.getProperty("password"));
        dataBase.sendKeys(EdgeTestProperties.getProperty("db"));
    }        

    //some more tests here        

}

My step definitions login tests:

@ScenarioScoped
public class LoginSteps {

    private LoginPage login;

    @Inject
    LoginSteps(LoginPage login) {
    this.login = login;
    }

    @Given("^I'm on the login page$")
        public void getLoginPage() throws FileNotFoundException {
        login.get();    
    }

    @When("^I enter valid user credential")
    public void enterValidUserCredential() throws FileNotFoundException {
        login.enterValidLoginCredentials();
    }

In my CucumberModule I used:

public class CucumberModule extends AbstractModule {
    @Override
    protected void configure() {
        requestStaticInjection(BasePage.class);     
    }

    @Provides
    @ScenarioScoped
    WebDriver getWebDriver(){
        WebDriver driver = null;

        try {
            driver =  Browser.loadDriver(EdgeTestProperties.getProperty("browser"));
        } catch (FileNotFoundException e) {
            System.err.print("Failed to load the browser preference, check test properties file....");
            e.printStackTrace();
        }
        driver.manage().timeouts().implicitlyWait(2L, TimeUnit.SECONDS);    

        return driver;
    }
}

When I start running the login test, the @Injection of the driver on BasePage only happens after the base constructor is called (initialization of the WebElements). As a result, none of the WebElements are initialized, so I get NullPointerException. I could get it to work if I remove the super class constructor and change the driver injection point to the LoginPage constructor and pass it back to the BasePage by a method which sets the driver and the initialise WebElements, however that seems wrong. Is there a better way?


Answer:

You should use Constructor injection.

This will solve your problem, as the driver will now be available in the constructor.

protected final Webdriver driver;

@Inject
public BasePage(WebDriver driver) {
    this.driver = driver;
    PageFactory.initElements(this.driver, this);
}

You should be aware that, generally speaking, field injection is not preferred. You have discovered one reason; another reason is that it's hard to test. Constructor injections are more verbose, but it also makes it much more clear what's going on, both when reading the code and when trying to write tests.

Question:

I have 4 step definition classes and a set of domain object classes. My first step definition class looks like this:

public class ClaimProcessSteps {
    Claim claim;

    public ClaimProcessSteps(Claim w){
        this.claim = w;
    }

    @Given("^a claim submitted with different enrolled phone's model$")
    public void aClaimSubmittedFromCLIENTSChannelWithDifferentEnrolledPhoneSModel() throws Throwable {
        claim = ObjMotherClaim.aClaimWithAssetIVH();
    }


}

My Claim class looks like this:

public class Claim {
    private String claimType;
    private String clientName;                  
    private Customer caller;
    private List<Hold> holds;

    public Claim() {}

    public Claim(String claimType, String clientName, Customer caller) {
        this.claimType              =       claimType;
        this.clientName             =       clientName;
        this.caller                 =       caller;
    }

    public String getClaimType() {
        return claimType;
    }

My second step definition class looks like:

public class CaseLookupSteps {
    Claim claim;

    public CaseLookupSteps(Claim w){
        this.claim = w;
    }

    @When("^I access case via (right|left) search$")
    public void iAccessCaseInCompassViaRightSearch(String searchVia) throws Throwable {
       System.out.println(claim.getClaimType());
    }

I've already imported the picocontainter dependency in my POM.XML and I am getting the following error.

3 satisfiable constructors is too many for 'class java.lang.String'. Constructor List:[(Buffer), (Builder), ()]

None of my step definition classes constructors receive primitives as arguments. Does anyone have any clue as to why I am still getting that error? Could it be my business object constructor that does expect a String in its constructor?

Thanks in advance for any help.


Answer:

Assuming your scenario looks like this:

Given some sort of claim
When I lookup this claim
Then I see  this claim

Currently your test is missing the setup step of the claim.

So rather then directly sharing the claim object between steps you should create a ClaimService class with only the default constructor. You can inject this service into your step definitions.

Once you have injected the service, you can use it in the step definition of Given some sort of claim to callclaimService.createSomeSortOfClaim() to create a claim. This claim can be created in memory, in a mock db, actual db, or other persistence medium.

In When I lookup this claim you then use claimService.getClaim() to return that claim so you can use its type to search for it.

Doing it this way you'll avoid the difficulty of trying to make the DI container figure out how it should create the claim under test.

Question:

I need to achieve dependency injection using PicoContainer without passing constructor parameters, current setup:

public class Shared_Data  {

    public Account_Pojo account_pojo;

    public Shared_Data(Account_Pojo account_pojo) {
        this.account_pojo = account_pojo;
    }

In the above example I need to achieve DI using PicoContainer without passing: Account_Pojo account_pojo as a parameter to the constructor, is this even possible?

I have tried the following with no luck:

public class Shared_Data {
    public Account_Pojo account_pojo;

    public Shared_Data() {
    }

    public void setAccount_pojo(Account_Pojo account_pojo) {
        this.account_pojo = account_pojo;
    }

    public Account_Pojo getAccount_pojo() {
        setAccount_pojo(account_pojo);
        return account_pojo;
    }
}

Answer:

You can set up a pico container with a SetterInjection component factory[0].

pico = new DefaultPicoContainer(new SetterInjection());
pico.addComponent(Account_Pojo.class);

Something like this should work.

[0] http://picocontainer.com/setter-injection.html

Question:

How to effectively use PicoContainer Setter Injection?

I need to be able to pass data across Cucumber steps and want to avoid the traditional PicoContainer approach where parameters are passed to the constructor (Use PicoContainer without taking the approach of injecting classes inside of other classes via the constructor).

So far I have the following setup with no luck, any ideas?

My steps class:

public class MainSteps extends Base {

    DefaultPicoContainer pico = new DefaultPicoContainer(new SetterInjection("init"));

    public MainSteps() {
        this.pico.addComponent(Account_Pojo.class);
    }

    @Given("^I went to the farmm$")
    public void i_went_to_the_farm() {
        loadUrl(Global_Vars.HOMEPAGE_URL);
        Account_Pojo account_pojo = pico.getComponent(Account_Pojo.class);
        account_pojo.setFirstName("joe");
        System.out.println(account_pojo.getFirstName()); //outputs joe
    }

    @Given("^I went to the zoo$")
    public void i_went_to_the_zoo() {
        Account_Pojo account_pojo = pico.getComponent(Account_Pojo.class);
        System.out.println(account_pojo.getFirstName()); //null pointer exception
    }
    }

Pojo Setup:

public class Account_Pojo {
    private String firstName;

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getFirstName() {
        return firstName;
    }
}

As you can see from the above code, the following line of code situated within the second step should return joe however it seems to be returning a null value instead:

System.out.println(account_pojo.getFirstName()); //null pointer exception

Answer:

Change the firstName to static like

private static String firstName;

OR

Pass the same object reference where you doing

System.out.println(account_pojo.getFirstName());