How to add a bean in SpringBootTest - java

The question seems extremely simple, but strangely enough I didn't find a solution.
My question is about adding/declaring a bean in a SpringBootTest, not overriding one, nor mocking one using mockito.
Here is what I got when trying the simplest implementation of my real need (but it doesn't work):
Some service, bean, and config:
#Value // lombok
public class MyService {
private String name;
}
#Value // lombok
public class MyClass {
private MyService monitoring;
}
#Configuration
public class SomeSpringConfig {
#Bean
public MyClass makeMyClass(MyService monitoring){
return new MyClass(monitoring);
}
}
The test:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = { SomeSpringConfig.class })
public class SomeSpringConfigTest {
private String testValue = "testServiceName";
// this bean is not used
#Bean
public MyService monitoringService(){ return new MyService(testValue); }
// thus this bean cannot be constructed using SomeSpringConfig
#Autowired
public MyClass myClass;
#Test
public void theTest(){
assert(myClass.getMonitoring().getName() == testValue);
}
}
Now, if I replace the #Bean public MyService monitoring(){ ... } by #MockBean public MyService monitoring;, it works. I find it strange that I can easily mock a bean, but not simply provide it.
=> So how should I add a bean of my own for one test?
Edit:
I think ThreeDots's answer (create a config test class) is the general recommendation.
However, Danylo's answer (use #ContextConfiguration) fit better to what I asked, i.e. add #Bean directly in the test class.

Spring Test needs to know what configuration you are using (and hence where to scan for beans that it loads). To achieve what you want you have more options, the most basic ones are these two:
Create configuration class outside the test class that includes your bean
#Configuration
public class TestConfig {
#Bean
public MyService monitoringService() {
return new MyService();
}
}
and then add it to to test as configuration class #SpringBootTest(classes = { SomeSpringConfig.class, TestConfig.class })
or
If you only need to use this configuration in this particular test, you can define it in static inner class
public class SomeSpringConfigTest {
#Configuration
static class ContextConfiguration {
#Bean
public MyService monitoringService() {
return new MyService();
}
}
}
this will be automatically recognized and loaded by spring boot test

Simply add the config as
#ContextHierarchy({
#ContextConfiguration(classes = SomeSpringConfig.class)
})

What i am using in this cases is #Import:
#DataJpaTest(showSql = false)
//tests against the real data source defined in properties
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
#Import(value = {PersistenceConfig.class, CustomDateTimeProvider.class})
class MessageRepositoryTest extends PostgresBaseTest {
....
Here i am using a pre configured "test slice".
In this case a need to add my JpaAuditingConfig.
But why not just adding the other beans as you did with your SomeSpringConfig.class ?:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = { SomeSpringConfig.class, OtherBean.class })
public class SomeSpringConfigTest {
...
Everything listed in test will be injectable directly, all not declared must be added as mocks.

Related

Spring Bean override with annotations in parent-child relationship

I have a two classes which are in a parent-child relation, the second one being a mock of the first.
public class A {
public void doSomething(){...};
}
public class MockA extends A {
#Override
public void doSomething() {...};
}
I also have two #Configuration classes one for development environment and one for test, the test one just mocks a couple of behaviors, but it imports the development one. Either way, I want that in test environment for MockA to be injected and in development for class A to be injected in other services which autowire it.
I can do that if I overwrite the bean in the test configuration. The following will work:
#Configuration
public class ApplicationConfig {
#Bean
public A beanA() {
return new A();
}
}
#Configuration
public class TestApplicationConfig {
#Bean
public A beanA() {
return new MockA();
}
}
However, I do not want to create the beans in the #Configuration class. I want to put the #Component annotation on each one and let them be injected in services correctly.
I've tried two approaches
1) Creating a dummy annotation, adding the annotation on the class A and trying to exclude the bean from the TestApplicationConfig.
#Configuration
#ComponentScan(
basePackages = {
"murex.connectivity.tools.interfaces.monitor.cellcomputer",
"murex.connectivity.tools.interfaces.monitor"
}, excludeFilters = #ComponentScan.Filter(type = FilterType.ANNOTATION, classes =
MyAnnotation.class))
public class TestApplicationConfig {
}
2) using #Component and #Primary annotations on the MockA class. My logic being that in the case both are present (which will happen only on the test case, because only then the MockA is scanned), the MockA will be injected everywhere. But this does not happen.
I am wondering if I am doing something wrong or is this a limitation from Spring? The #Primary annotation seems to be constructed exactly for this specific case, am I mistaken? Is the fact that the two classes have a parent-child relationship that is the problem?
L.E. Using two different profiles will work. I am more curious if my understanding of the two presented approaches is correct and/or if this is a limitation on Spring using #Component annotations
Tried to combine two suggested approaches. It worked for me:
#Component
#Primary
public class ClassA {
public void doSomething() {
System.out.println("A");
}
}
#Component
public class ClassB extends ClassA {
#Override
public void doSomething() {
System.out.println("B");
}
}
#Component
public class ClassC {
#Autowired
public ClassA component;
}
All three classes are in same package.
#Configuration
#ComponentScan(value = "com.test", excludeFilters = #ComponentScan.Filter(type = FilterType.ANNOTATION, classes =
Primary.class))
public class RandomConfig {
}
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = {RandomConfig.class})
public class RandomTest {
#Autowired
ClassC c;
#Test
void when_then() {
//prints "B"
c.component.doSomething();
}
}
So RandomConfig excludes all #Primary beans, whereas production config uses only #Primary
You can use profiles.
#Profile("!test")
#Configuration
public class ApplicationConfig {
#Bean
public A beanA() {
return new A();
}
}
And for test cases:
in test resources in application.properties: spring.profiles.active=test or on test class #ActiveProfiles("test") and create configuration:
#Profile("test")
#Configuration
public class TestApplicationConfig {
#Bean
public A beanA() {
return new MockA();
}
}

Overriding spring #Configuration in an integration test

I have a spring boot configuration class like this:
#Configuration
public class ClockConfiguration {
#Bean
public Clock getSystemClock() {
return Clock.systemUTC();
}
}
and I have some integration tests like this:
#SpringBootTest(classes = MyApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT)
public abstract class AbstractIntegrationTest {
}
and tests like this:
public class MiscTests extends AbstractIntegrationTest{
#Test
public void CreateSomethingThatOnlyWorksInThe Morning_ExpectCorrectResponse() {
}
I want to be able to offset the clock bean to run some tests at different times on the day. How do I do this?
NOTE: I see several stack overflow answers similar to this, but I can't get them to work.
Based on other responses, it appears the solution should be something like:
#SpringBootTest(classes = MyApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT)
public abstract class AbstractIntegrationTest {
#Configuration
class MyTestConfiguration {
#Bean
public Clock getSystemClock() {
Clock realClock = Clock.systemDefaultZone();
return Clock.offset(realClock, Duration.ofHours(9));
}
}
}
But nothing happens there. Do I need to #Import something? do I need to #Autowired something?
Thanks!
As you are using Spring Boot you can take advantage of the #MockBean annotation:
#SpringBootTest(classes = MyApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT)
public abstract class AbstractIntegrationTest {
#MockBean
private Clock clockMock;
}
Then you can stub public methods of that bean an each of the tests accordingly and uniquely:
#Test
public void CreateSomethingThatOnlyWorksInThe Morning_ExpectCorrectResponse() {
when(clockMock.getTime()).thenReturn(..);
}
As per javadoc of #MockBean:
Any existing single bean of the same type defined in the context will
be replaced by the mock.
it is the #TestConfiguration annotation that you need https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/context/TestConfiguration.html
#RunWith(SpringRunner.class)
public class ClockServiceImplIntegrationTest {
#TestConfiguration
static class TestOverridingClockServiceConfiguration {
#Bean
public ClockService clockService() {
return new ClockServiceImpl();
}
}
#Autowired
private ClockService clockService;
#MockBean
private ClockRepository clockRepository;
// write test cases here
}
In the case you have existing configuration you c

Spring/TestNG integration: Auto-injection fails with two test classes

Hitting an odd issue with my spring based integration tests. Note that I'm not so much unit testing a spring IoC app as I'm using Spring to auto-inject properties into my test configuration. Also - we're not using any context configuration xml, everything is configured programatically.
I got the framework all set up and running with a single test before handing it off to the team tester, auto-injection was working fine. However, as soon as he added a second test class, auto-injection of my configuration field just stops working. I've cloned his branch and verified the behavior; Commenting out his class OR my original class causes the auto-injection to return to normal function.
Anybody ever seen this? Relevant code snippets below. I've tried moving around the #ContextConfiguration annotation but it didn't really help. It seems like Spring is having trouble deciding where the TestProperties object should come from. The field I've commented on below is coming up null when I run the tests, but only if there are two test classes enabled.
#ContextConfiguration(classes = TestConfig.class, loader = AnnotationConfigContextLoader.class)
public abstract class BaseIntegrationTest
extends AbstractTestNGSpringContextTests
{
#Autowired
protected MockServerClient mockServer;
// I get an error in IntelliJ Pro on this field.
// Could not autowire. There is more than one bean of 'TestProperties' type. Beans: getTestProperties, testProperties
// I've also tried #Qualifier("getTestProperties") and #Qualifier("testProperties")
// which both resolve the error in IntelliJ but the TestProperties field is still null when running the tests.
#Autowired
protected TestProperties properties;
//...
}
public class TestGetMuIT
extends BaseIntegrationTest
{
private ManagementUnitClient managementUnitClient;
#BeforeMethod(alwaysRun = true)
public void setup()
{
String endpoint = properties.getServiceBaseUri(); //NullPointerException thrown here during test run
}
}
Other relevant classes:
#Configuration
public class RootConfig
{
protected static final Logger LOGGER = LoggerFactory.getLogger(RootConfig.class);
private PropertyService propertyService;
private TestProperties testProperties;
#Bean
public PropertyService propertyService() throws IOException {
if (propertyService == null) {
propertyService = new DynamoDbPropertyService(config.getPropertiesConfig());
propertyService.initialize();
}
return propertyService;
}
#Bean
public TestProperties getTestProperties() throws IOException {
if (testProperties == null) {
testProperties = new TestProperties();
}
}
And the actual TestProperties class:
#Component
public class TestProperties
{
public static class Default {
public static final String BASE_URI = "http://localhost:8055";
}
#Autowired
private PropertyService propertyService;
public String getServiceBaseUri()
{
return propertyService.getPropertyRegistry().getString(TestConfigKey.LocalServiceBaseUri.key(), Default.BASE_URI);
}
}
And these are my spring config annotations:
#Configuration
//Note: TestProperties.class is a recent addition. I added that and commented out the #Bean getTestProperties() method in RootConfig as a troubleshooting step
#Import({RootConfig.class, TestProperties.class})
#EnableAspectJAutoProxy
#ComponentScan(basePackages = {"com.inin.wfm.it"},
excludeFilters = #ComponentScan.Filter(value = com.inin.wfm.it.config.ConfigPackageExcludeFilter.class, type = FilterType.CUSTOM))
public class TestConfig {\\... }
Again all of the above works just fine if there's only one test file (and it doesn't matter which of the two I comment out). Anybody know what I'm doing wrong in my configuration?

reduce spring ContextConfiguration boilerplate in tests

I was wondering if there is a way to reduce the amount of boilerplate that we are currently writing for out integration tests.
The main culprit is ContextConfiguration that we send 7 distinct strings into currently.
One of our tests looks like this (payload code removed):
#ContextConfiguration(locations = {"classpath:properties-config.xml",
"classpath:dataSources-config.xml",
"classpath:dao-config.xml",
"classpath:services-config.xml",
"classpath:ehcache-config.xml",
"classpath:test-config.xml",
"classpath:quartz-services.xml"})
#RunWith(SpringJUnit4ClassRunner.class)
#Category(IntegrationTest.class)
public class TerminalBuntsPDFTest {
#Autowired
private JobService jobService;
#Test
public void testCode() throws SystemException {
assertTrue("Success", true);
}
}
And the specification of what xml files to load takes up a lot of space. We are in a (very slow) process of migrating away from xml towards annotations, but there is a lot of work left to do in that project.
We are using spring 3.2.
The annotation based approach is to create a Spring Configuration Java class like this:
#Configuration("testConfig")
#ImportResource({
"dataSources-config.xml",
"dao-config.xml",
"services-config.xml"
})
public class TestConfiguration {
// TO create a spring managed bean
#Bean
MyBean myBean() {
return new MyBean();
}
}
Then you can annotate your test class like so to load the configuration:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(
classes=TestConfiguration.class,
loader=AnnotationConfigContextLoader.class
)
#Category(IntegrationTest.class)
public class TerminalBuntsPDFTest {
This is just a light example that probably won't compile but should get you on the right track
Some relevant docs:
http://www.tutorialspoint.com/spring/spring_java_based_configuration.htm
http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Configuration.html
What about such pattern:
#ContextConfiguration(locations = {"classpath:properties-config.xml",
"classpath:dataSources-config.xml",
"classpath:dao-config.xml",
"classpath:services-config.xml",
"classpath:ehcache-config.xml",
"classpath:test-config.xml",
"classpath:quartz-services.xml"})
#RunWith(SpringJUnit4ClassRunner.class)
#Category(IntegrationTest.class)
public abstract class BaseTest {
}
// ....
public class TerminalBuntsPDFTest extends BaseTest {
#Autowired
private JobService jobService;
#Test
public void testCode() throws SystemException {
assertTrue("Success", true);
}
}
// ....
public class TerminalBuntsPDFTest2 extends BaseTest {}
This will allow you to place configuration only once in parent abstract class.

XML-less configuration for spring

I have the following configuration bean for a non web app
#Configuration
public class MyBeans {
#Bean
#Scope(value="prototype")
MyObject myObject() {
return new MyObjectImpl();
}
}
On the other side I have my class
public class MyCommand implements Command {
#Autowired
private MyObject myObject;
[...]
}
How can I make myCommand be autowired with the configuration in MyBeans without using XML so I can inject mocks in my other test classes?
Thanks a lot in advance.
With XML-based configuration you'd use the ContextConfiguration annotation. However, the ContextConfiguration annotation doesn't appear to work with Java Config. That means that you have to fall back on configuring your application context in the test initialization.
Assuming JUnit4:
#RunWith(SpringJUnit4ClassRunner.class)
public class MyTest{
private ApplicationContext applicationContext;
#Before
public void init(){
this.applicationContext =
new AnnotationConfigApplicationContext(MyBeans.class);
//not necessary if MyBeans defines a bean for MyCommand
//necessary if you need MyCommand - must be annotated #Component
this.applicationContext.scan("package.where.mycommand.is.located");
this.applicationContext.refresh();
//get any beans you need for your tests here
//and set them to private fields
}
#Test
public void fooTest(){
assertTrue(true);
}
}

Categories

Resources