Eoin
Eoin

Reputation: 350

Can I create a spring bean as part of my cucumber step definitions?

For some context, my spring boot application makes calls to external APIs providing the current date using LocalDate.now() and retrieves various information back. We are using Cucumber for testing and the above presents a problem when writing step definitions such as

Given external api "/some/endpoint/2021-04-21" returns csv
  | currency |
  | GBP      |

The step definition test code uses wiremock to mock that call, but since we are using LocalDate.now() in the production code, the test will fail for any day other than 2021-04-21.

The way I have gotten around this is by defining two beans for Clock that we can then autowire into the services that require them and use LocalDate.now(clock). The "real" bean is defined like so:

@Configuration
public class ClockConfiguration {
    @Bean
    public Clock clock() {
        return Clock.systemDefaultZone();
    }
}

And the test bean like so:

@Profile("cucumber")
@TestConfiguration
public class ClockConfiguration {
    @Bean
    public Clock clock() {
        return Clock.fixed(Instant.parse("2021-02-26T10:00:00Z"), ZoneId.systemDefault());
    }
}

This solves my problem and allows me to set a defined time for my tests, but my problem is that the date/time is defined in the test configuration. I would like to define it as part of my step definitions. For example something like

Given now is "2021-02-26T10:00:00Z"

Have a step definition along the lines of

@Given("now is {string})
public void setDateTime(String dateTime) {
    //Create Clock bean here...
}

Is there a way for me to do this? Or to even overwrite the existing bean during the cucumber step?

Upvotes: 2

Views: 1778

Answers (2)

Eoin
Eoin

Reputation: 350

I managed to find two working solutions to this problem. The first was a bit more complicated but I essentially created a wrapper class around Clock, so an interface with a method Clock getClock() which then had two implementations. One was the real bean that would just return Clock.systemDefaultZone() and a test bean that had a private Clock member that the getter would just return, but most important a setter like

public void setClock(String dateTime) {
    this.clock = Clock.fixed(Instant.parse(dateTime), ZoneId.systemDefault());
}

I would then call this setter as part of step definition method by getting the bean e.g.

@Given("now is {string})
public void setDateTime(String dateTime) {
    applicationContext.getBean(MyWrapperClock.class).setClock(dateTime);
}

The 2nd method I found was much simpler, and was to simply mock the clock in the test configuration file e.g.

@Bean 
public Clock clock() {
    return Mockito.mock(Clock.class);
}

And then autowire this in to the step definition class and just do a normal mockito when such as

@Given("now is {string})
public void setDateTime(String dateTime) {
    when(this.clock.instant()).thenReturn(Instant.parse(dateTime));
    when(this.clock.getZone()).thenReturn(ZoneId.systemDefault());
}

Upvotes: 1

Yeshwanth Rajaram
Yeshwanth Rajaram

Reputation: 11

Try replacing the date with a variable or a static method the response of which can be altered by the step definition.

@Profile("cucumber")
@TestConfiguration
public class ClockConfiguration {
    @Bean
    public Clock clock() {
        return Clock.fixed(Instant.parse(TestScope.getDateValue()), ZoneId.systemDefault());
    }
}
@Given("now is {string})
public void setDateTime(String dateTime) {
    TestScope.setDateValue(dateTime);
}

Upvotes: 1

Related Questions