edwise
edwise

Reputation: 888

How to unit test an Spring @Bean CommandLineRunner?

I'm using Spring Boot in a little PoC, and I'm trying to test a @Bean implementation. I have this code:

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    CommandLineRunner init(@Value("${db.resetAndLoadOnStartup:true}") boolean resetAndLoadOnStartup,
                           SequenceIdRepository sequenceRepository,
                           UserAccountRepository userAccountRepository,
                           BookRepository bookRepository) {
        return args -> {
            log.info("Init Application...");
            if (resetAndLoadOnStartup) {
                fillDBData(sequenceRepository, userAccountRepository, bookRepository);
            }
            log.info("Aplication initiated!");
        };
    }

    private void fillDBData(SequenceIdRepository sequenceRepository,
                            UserAccountRepository userAccountRepository,
                            BookRepository bookRepository) {
        // Some code...
    }
...
}

How can I unit test this @Bean commandLineRunner? Yeah, maybe I could unit test the 'fillDBData' method (putting protected or with powermock), but I would like to learn if there's a way to test the Spring @Bean "completely".

Upvotes: 8

Views: 18838

Answers (4)

OrangeDog
OrangeDog

Reputation: 38777

In general, to test beans you simply inject them into a Spring test. For example:

@SpringBootTest
public class FullIntegrationTest {
    @Autowired CommandLineRunner runner;
    // ...
@SpringJUnitConfig(TestConfig.class)
public class SingleUnitTest {
    @Autowired CommandLineRunner runner;

However, the problem you have is that the runner will be executed on context startup, before any tests are run. The only way to prevent that is to not have the runner as a bean, but then it cannot be injected. It's neater if you split things up:

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    @Profile("!test")
    CommandLineRunner init(DbService dbService) {
        return dbService::init;
    }
}
@Service
public class DbService {
    @Value("${db.resetAndLoadOnStartup:true}") boolean resetAndLoadOnStartup;

    public void init(String... args) {
       log.info("Init Application...");
       // ...
    }

    private void fillDBData(
       // ...
@SpringBootTest
@ActiveProfiles("test")
public class FullIntegrationTest {
    @Autowired DbService runner;
    // ...

You might like to add an interface and/or a @ConfigurationProperties to separate things further and make future additions and testing easier.

Upvotes: 0

VZoli
VZoli

Reputation: 602

Create MyApplication class

package com.company.project;
//imports
@SpringBootApplication(scanBasePackages = "com.company.project")
public class FileToDbApplication {
  public static void main(String[] args) {
    SpringApplication.run(FileToDbApplication.class, args);
  }
}

Create MyCommandLineRunner class

package com.company.project;
//imports
@Component
public class MyCommandLineRunner implements CommandLineRunner {
  @Autowired
  private SurValueService surValueService;

  @Override
  public void run(String... args) {
    System.out.println("App started with " + args.length + " parameters.");
  }
}

Create MyConfiguration class (you may not need @EntityScan and @EnableJpaRepositories)

package com.company.project;
//imports
@SpringBootConfiguration
@ComponentScan("com.company.project")
@EnableAutoConfiguration
@EntityScan(basePackages = "com.company.project.model")
@EnableJpaRepositories(basePackages = "com.company.project.service")
public class MyConfiguration {

}

Create MyCommandLineRunnerTest in src.test.java

package com.company.project;
//imports
@ExtendWith(SpringExtension.class)
@SpringBootTest
class MyCommandLineRunnerTest {

  @Autowired
  MyCommandLineRunner commandLineRunner;

  @Test
  public void testMain() {
    commandLineRunner.run("input.txt", "input2.txt");
  }
}

Upvotes: 0

gyoder
gyoder

Reputation: 4738

Here's how you could test with an integration test.

@RunWith(SpringJUnit4ClassRunner.class)
// Or create a test version of Application.class that stubs out services used by the CommandLineRunner
@SpringApplicationConfiguration(classes = Application.class)
public class CommandLineRunnerIntegrationTest {

    @Autowired
    private CommandLineRunner clr;

    @Test
    public void thatCommandLineRunnerDoesStuff() throws Exception {
        this.clr.run();
        // verify changes...
    }

}

That being said, my preference would be to create a named service that implements command line runner and then unit test it with all of its dependencies mocked out. In my opinion, it's not critical to test that Spring is going to call the CommandLineRunner bean when the application loads, but that the CommandLineRunner implementation calls other services appropriately.

Upvotes: 4

Eddú Meléndez
Eddú Meléndez

Reputation: 6530

You can use OutputCapture to see what you print in the console

@Rule
public OutputCapture outputCapture = new OutputCapture();

in your test method:

String output = this.outputCapture.toString();
    assertTrue(output, output.contains("Aplication initiated!"));

Upvotes: 4

Related Questions