bcr666
bcr666

Reputation: 2197

How to use dependency-injection with SpringBootTest

I have an application that uses SpringBoot for dependency injection and the app works fine, but testing fails because @Autowired fields aren't being injected during tests.

@SpringBootApplication
public class ProcessorInterface {
    protected final static Logger logger = Logger.getLogger( ProcessorInterface.class );

    public static void main(String[] args) {
        try {
            SpringApplication.run(ProcessorInterfaceRunner.class, args);
        } catch (Exception ex) {
            logger.error("Error running  ProcessorInterface", ex);
        }
    }
}


@Component
@Configuration
@ComponentScan
public class ProcessorInterfaceRunner implements CommandLineRunner {
    protected final static Logger logger = Logger.getLogger( ProcessorInterface.class );

    @Autowired
    private RequestService requestService = null;
    @Autowired
    private ValidatorService validatorService = null;

    @Override
    public void run(String... args) throws Exception {

        ESPOutTransaction outTransaction = null;
        outTransaction = new ESPOutTransaction();
        // initialize outTransaction fields
        ...
        // done initializing outTransaction fields

        if (validatorService.isValid(outTransaction)) {
            System.out.println(requestService.getRequest(outTransaction));
        } else {
            System.out.println("Bad Data");
        }
    }
}


@Service
public class ESPRequestService implements RequestService<ESPOutTransaction> {

    @Autowired
    ValidatorService validatorService = null;

    @Override
    public String getRequest(ESPOutTransaction outTransaction) throws IllegalArgumentException {
        if (!validatorService.isValid(outTransaction)) {
            throw new IllegalArgumentException("Invalid parameters in transaction object. " + outTransaction.toString());
        }

        StringBuffer buff = new StringBuffer("create request XML");
        buff.append("more XML");
        return buff.toString();
    }
}


@Service
public class ESPValidatorService implements ValidatorService {
    private static org.apache.log4j.Logger logger = Logger.getLogger(ESPValidatorService.class);

    // declare some constants for rules
    private static final int MAX_LENGTH_XYZ = 3;

    @Override
    public boolean isValid(OutTransaction outTransaction) {
        ESPOutTransaction espOutTransaction = (ESPOutTransaction)outTransaction;
        boolean isValid = true;

        if (espOutTransaction == null) {
            logger.warn("espOutTransaction is NULL");
            isValid = false;
        } else {
            // XYZ is required
            if (espOutTransaction.getXYZ() == null) {
                logger.warn("XYZis NULL\r\n" + espOutTransaction.toString());
                isValid = false;
            }
            // XYZ max length = MAX_LENGTH_XYZ
            if (espOutTransaction.getXYZ() != null && espOutTransaction.getPubCode().trim().length() > MAX_LENGTH_XYZ) {
                logger.warn("XYZis too long (max length " + MAX_LENGTH_XYZ + ")\r\n" + espOutTransaction.toString());
                isValid = false;
            }
        }
        return isValid;
    }
}

These all work and I get good output when I run the app. When I try to test it though, it fails because it can't find ESPValidatorService to inject into ESPRequestService

@RunWith(Suite.class)
@SuiteClasses({ ESPOutTransactionValidatorTest.class, ESPRequestTest.class })
public class AllTests {}


@RunWith(SpringRunner.class)
@SpringBootTest(classes = {ESPRequestService.class})
public class ESPRequestTest {

    @Test
    public void testGetRequest() {
        ESPRequestService requestService = new ESPRequestService();

        String XYZ = "XYZ";

        ESPOutTransaction outTransaction = null;
        outTransaction = new ESPOutTransaction();
        outTransaction.setXYZ(XYZ);

        String strRequest = "some expected request XML";
        String request = requestService.getRequest(outTransaction);
        assertEquals(request, strRequest);
    }
}


@RunWith(SpringRunner.class)
@SpringBootTest(classes = ESPValidatorService.class)
public class ESPOutTransactionValidatorTest {

    @Test
    public void testIsValid() {
        ESPValidatorService validatorService = new ESPValidatorService();

        ESPOutTransaction outTransaction = null;
        // test request = null
        assertFalse(validatorService.isValid(outTransaction));

        String XYZ = "XYZ";

        outTransaction = new ESPOutTransaction();
        outTransaction.setXYZ(XYZ);

        // test all good
        assertTrue(validatorService.isValid(outTransaction));

        // test XYZ
        outTransaction.setXYZ(null);
        assertFalse(validatorService.isValid(outTransaction));
        outTransaction.setXYZ("ABCD"); // too long
        assertFalse(validatorService.isValid(outTransaction));
        outTransaction.setXYZ(XYZ);
    }
}

How can I get the unit tests to auto wire?

Upvotes: 3

Views: 15414

Answers (1)

davidxxx
davidxxx

Reputation: 131546

I see two problems :

1) you don't rely on Spring beans but you create instances with the new operator.

Instead of writing :

ESPRequestService requestService = new ESPRequestService();

you should let Spring inject the instance :

@Bean
ESPRequestService requestService;

2) The @SpringBootTest configuration is not correct.

In each test, you specified a very specific bean class in the classes attribute of @SpringBootTest :

@SpringBootTest(classes = ESPValidatorService.class)
public class ESPOutTransactionValidatorTest {

and

@SpringBootTest(classes = {ESPRequestService.class})
public class ESPRequestTest {

But classes attributes of @SpringBootTest serves to specify the annotated classes to use for loading an ApplicationContext.

The annotated classes to use for loading an ApplicationContext. Can also be specified using @ContextConfiguration(classes=...). If no explicit classes are defined the test will look for nested @Configuration classes, before falling back to a SpringBootConfiguration search.

So all configuration classes and beans of your application may not be discovered and loaded in the Spring container .

To be able to load all application beans during your tests, the most simple way is not specifying the classes attribute in the @SpringBootTest annotation :

@SpringBootTest
public class ESPRequestTest { ...}

It will look for a Spring bean that holds the @SpringBootConfiguration.
Ideally, it will found the @SpringBootApplication bean of your application.
If the package of the test class is located inside the package (or at a lower level) of the @SpringBootApplication class, it should be automatically discovered.

Otherwise the other way is specifying a configuration that will allow to load all required beans :

@SpringBootTest(classes = MySpringBootApplication.class)
public class ESPRequestTest { ...}

Upvotes: 4

Related Questions