I have the following spring test configuration with different profiles:
#Configuration
#ComponentScan (value = {"uk.co.test.ws" })
public class SpringTestConfig
{
#Profile( "local")
#PropertySource( "classpath:/config/local/settings.properties" )
public static class SpringTestConfigLocal
{
#Autowired
private Environment environment ;
#Bean(name= "baseUrl" )
public String setBaseUrl()
{
return environment .getRequiredProperty("baseurl.protocol" )+"://" +environment .getRequiredProperty( "baseurl.host");
}
}
and then created a base class that takes in the base url
> #RunWith (SpringJUnit4ClassRunner. class) #ContextConfiguration
> (classes = { SpringTestConfig. class }) public class BaseWsTest {
> #Autowired
> String baseUrl;
which then gets extended to other test classes like below:
public class SampleFTest extends BaseWsTest
{
#Test
public void hello() throws FileNotFoundException, Exception
{
System. out .println("base url: " + baseUrl );
When run using normal maven clean install the tests works but if I was to run it by right-clicking the method it gets a
Error creating bean with name 'uk.co.test.ws.service.base.SampleFTest': Injection of autowired dependencies failed;
You forgot to choose the profile in your SampleFTest class, you should add this line:
#ActiveProfiles(profiles = "local")
In this way the SpringTestConfigLocal will be initialized and the baseUrl bean available
EDIT: I would add a property in a .properties file so I could use a variable myprofile:
#ActiveProfiles(profiles = "${myprofile}")
And eventually, if you don't want to change the value from time to time, I would apply some logic in order to load one property file or another.
EDIT 2: I'm sorry but this doesn't work because the file is loaded after the assigment of the annotation EL value, but you can add this:
spring.profiles.active=local
to the property file and this will do the same as putting the annotation #IntegrationTest("local"). This is the code I tried:
#TestPropertySource(value="classpath:/config/test.properties")//, properties={"myaddress=cane", "myprofile=local"})
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
#WebAppConfiguration
#IntegrationTest("${myaddress}")
//#ContextConfiguration
//#ActiveProfiles(profiles = "${myprofile}")
public class BasicJUnitTest{
protected Logger logger;
protected MockMvc mockMvc;
#Autowired
private Environment env;
#Value("${myaddress}" )
String myval;
public BasicJUnitTest(){
this.logger = LoggerFactory.getLogger(this.getClass());
}
#Test
public void test(){
logger.info("hola"+myval+ " " + env.getActiveProfiles()[0]);
}
}
Related
I'm working with Spring Boot 2.4.8, and I'm reading into a bean the information read from an external YML file:
#Component
#ConfigurationProperties(prefix = "my.conf")
#PropertySource(value = "classpath:ext.yml", factory = YamlPropertySourceFactory.class)
public class MyExternalConfProp {
private String property;
public void setProperty(String property) {
this.property = property;
}
public String getProperty() {
return property;
}
}
I defined a custom factory to read external YML files, as stated here in the article #PropertySource with YAML Files in Spring Boot:
public class YamlPropertySourceFactory implements PropertySourceFactory {
#Override
public PropertySource<?> createPropertySource(String name, EncodedResource encodedResource) {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(encodedResource.getResource());
Properties properties = factory.getObject();
return new PropertiesPropertySource(
Objects.requireNonNull(encodedResource.getResource().getFilename()),
Objects.requireNonNull(properties));
}
}
The content of the YML file is the following:
my.conf.property: yeyeye
The problem is that I cannot find a proper slice to test the configuration property in isolation. In fact, the following test fails:
#SpringBootTest(classes = {MyExternalConfProp.class})
class MyExternalConfPropTest {
#Autowired
private MyExternalConfProp confProp;
#Test
void externalConfigurationPropertyShouldBeLoadedIntoSpringContext() {
assertThat(confProp).hasFieldOrPropertyWithValue("property", "yeyeye");
}
}
As we said, the test fails with the following message:
java.lang.AssertionError:
Expecting
<in.rcard.externalconfprop.MyExternalConfProp#4cb40e3b>
to have a property or a field named <"property"> with value
<"yeyeye">
but value was:
<null>
Whereas, if I don't use any slice, the test succeeds:
#SpringBootTest
class ExternalConfPropApplicationTests {
#Autowired
private MyExternalConfProp confProp;
#Test
void contextLoads() {
assertThat(confProp).hasFieldOrPropertyWithValue("property", "yeyeye");
}
}
How can I resolve this? Is it some initializer or something similar that I can add to the slice to make the test succeed?
Here you can find the whole project on GitHub.
Add #EnableConfigurationProperties to your test or start the spring boot application on your test will solve the problem
#EnableConfigurationProperties
#SpringBootTest(classes = {MyExternalConfProp.class})
class MyExternalConfPropTest {
#Autowired
private MyExternalConfProp confProp;
#Test
void externalConfigurationPropertyShouldBeLoadedIntoSpringContext() {
assertThat(confProp).hasFieldOrPropertyWithValue("property", "yeyeye");
}
}
or
#SpringBootTest(classes = {YourSpringBootApplication.class})
class MyExternalConfPropTest {
#Autowired
private MyExternalConfProp confProp;
#Test
void externalConfigurationPropertyShouldBeLoadedIntoSpringContext() {
assertThat(confProp).hasFieldOrPropertyWithValue("property", "yeyeye");
}
}
I am writing unit tests on a Service class like below :
#Component
#Profile({"default", "dev"})
public class MyService {
#Value("${my.property}")
private String property;
private OtherService otherService;
public void MyMethod() {
String myVar = otherService.method();
return String.format("MyVar %s & MyProperty %s", myVar, property);
}
}
My current test class for this test is like this:
#ActiveProfiles("dev")
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = MyApplication.class)
public class MyServiceTest {
#Mock
private OtherService otherService;
#InjectMocks
private MyService myService;
#BeforeEach()
public void init() {
MockitoAnnotations.initMocks(this);
when(otherService.method()).thenReturn("a string");
}
#Test
public void shouldReturnException() {
final Exception exception = assertThrows(ApiErrorException.class,
() -> myService.myMethod(var));
assertThat(exception).hasMessage("here the message");
verify(otherService, never()).method();
}
}
With this two classes, I have an application.yml & application-dev.yml to set my.property.
I want to get the property from the application-dev file during my tests execution.
But, with #InjectMocks, property is null. Whereas, using #Autowired in place of/with #InjectMocks, the property variable is set with the value present in file.
Problem, using Autowired with/in place of InjectMock results in the otherService variable being initialized, so no mock is created.
How can I still use Mockito, while having the property variable set with the value in the file?
I saw about ReflectionTestUtils.setField, but using it mean having no use of a yml file (which i am not fan).
Have a nice day
With the help of #Deadpool, the tests can use the values written in the application.yml file.
But, using #MockBean and #Autowired, the tests get a behavior I do not understand.
Example:
I test that a method return an Exception, and I verify that others methods are not called after the exception was catch: verify(otherService, never()).otherMethod();
Writing this line returns the following error org.mockito.exceptions.verification.NeverWantedButInvoked.
The initial exception is correctly caught, but the test does not seem to acknowledge that no other services must be called.
#SpringBootTest is used for integration testing which loads the ApplicationContext that will be utilized for test environment
The #SpringBootTest annotation can be used when we need to bootstrap the entire container. The annotation works by creating the ApplicationContext that will be utilized in our tests.
Since you are generating integration environment using #SpringBootTest you need to mock the bean using #MockBean annotation
#ActiveProfiles("dev")
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = MyApplication.class)
public class MyServiceTest {
#MockBean
private OtherService otherService;
#Autowire
private MyService myService;
#BeforeEach
public void init() {
when(otherService.method()).thenReturn("a string");
}
}
I suggest you change your MyService class to accept the OtherService either via the constructor or via setter. Something like this:
#Component
#Profile({"default", "dev"})
public class MyService {
#Value("${my.property}")
private String property;
private OtherService otherService;
public MyService(OtherService otherService) {
this.otherService = otherService
}
public void MyMethod() {
String myVar = otherService.method();
return String.format("MyVar %s & MyProperty %s", myVar, property);
}
}
And then you do your test like this:
#ActiveProfiles("dev")
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = MyApplication.class)
public class MyServiceTest {
#Mock
private OtherService otherService;
#InjectMocks
#Autowired
private MyService myService;
#BeforeEach()
public void init() {
MockitoAnnotations.initMocks(this);
when(otherService.method()).thenReturn("a string");
}
}
I have a product.xml file I want to have 'injected' easily into my test class by using the #Value annotation of Spring.
#SpringBootTest
#TestPropertySource(properties = {
"test.file.product=product.xml"
})
public class XmlToJsonParserTest {
#Value("${test.file.product}")
private Resource product;
#Autowired
private XmlToJsonParser xlToJsonParser;
#Test
public void shouldParseFromXml() throws IOException {
JsonNode node = xmlToJsonParser.getNodeFromXML(product.getFile());
assertThat(node).isNotNull();
}
}
My testfile is under src/test/resources/product.xml
I managed to get that working before but just can't figure out how I did it or what setup I had back the day.
In this case product is always null
My Junit is not picking up properties set in test properties file.
I get no error, but value returned from properties file is null
CLASS TO BE TESTED:
package com.abc.mysource.mypackage;
#Component
#ComponentScan
public class MyHelper {
#Autowired
#Qualifier("commonProperties")
private CommonProperties commonProperties;
public LocalDateTime method1ThatUsesCommonProperties(LocalDateTime startDateTime) throws Exception {
String customUserType = commonProperties.getUserType(); // Returns null if run as JUnit test
//Further processing
}
}
SUPPORTING COMPONENTS - BEANS & CONFIG CLASSES:
package com.abc.mysource.mypackage;
#Component
public class CommonProperties {
#Value("${myhelper.userType}")
private String userType;
public String getUserType() {
return userType;
}
public void setCalendarType(String userType) {
this.userType = userType;
}
}
CONFIG CLASS:
package com.abc.mysource.mypackage;
#Configuration
#ComponentScan(basePackages ="com.abc.mysource.mypackage.*")
#PropertySource("classpath:default.properties")
public class CommonConfig {}
default.properties under src/main/resources
myhelper.userType=PRIORITY
MY TEST CLASS:
package com.abc.mysource.mypackage.test;
#RunWith(SpringRunner.class)
#ContextConfiguration(classes=MyHelper.class)
#TestPropertySource("classpath:default-test.properties")
#EnableConfigurationProperties
public class MyHelperTest {
#MockBean(name="commonProperties")
private CommonProperties commonProperties;
#Autowired
private MyHelper myHelper;
#Test
public void testMethod1ThatUsesCommonProperties() {
myHelper.method1ThatUsesCommonProperties();
}
}
default-test.properties defined under /src/test/resources:
myhelper.userType=COMMON
NOTE:
I moved default-test.properties to /src/main/resources - commonProperties.getUserType() is null
I even used #TestPropertySource(properties = {"myhelper.userType=COMMON"}). Same result
NOTE 2:
I tried the solution on #TestPropertySource is not loading properties.
This solution requires me to create a duplicate bean called CommonProperties under src/test/java. But #MockBean fails when I do
#MockBean(name="commonProperties")
private CommonProperties commonProperties;
Please do not mark a duplicate.
NOTE 3:
Mine is a spring, not a spring boot application.
MockBeans are suited if you don't need specific state. Usually this bean is "isolated" and every method call of this bean will have the same result. It is "isolated" -> the service that uses #Value annotation will not apply on this bean.
What you need is a "normal" bean, properly constructed and initialized. Please use #Autowired annotation and define another bean if needed, using a test profile.
My classes are..
lies in src/intregation-test/java
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, classes = StoreOrderFulfillmentApplication.class)
#ActiveProfiles("Test")
public class OrderCreationIntregationTest {
#Autowired
private TestRestTemplate restTemplate;
#MockBean
private OrderRepository orderRepository;
#MockBean
private OrderLineItemRepository orderLineItemRepository;
#MockBean
private InternalEventPublisher internalEventPublisher;
#SuppressWarnings("unchecked")
#Before
public void setup() {
Mockito.when(orderRepository.findByOfsReferenceId("OFS:GMO:Z100002062-99")).thenReturn(null);
OrderEntity savedOrder = new OrderEntity();
savedOrder.setOrderId(1023);
Mockito.when(orderRepository.save(Mockito.any(OrderEntity.class))).thenReturn(savedOrder);
Iterable<OrderLineItemEntity> orderLineItemList = prepareOrderLineItemEntityIterable();
Mockito.when(orderLineItemRepository.save(Mockito.any(Iterable.class))).thenReturn(orderLineItemList);
}
#Test
public void test() throws ParseException {
FulfillmentOrder fulfillmentOrderRequestVO = new FulfillmentOrder();
fulfillmentOrderRequestVO = buildFulfillmentOrder();
String myMessage = "Order Created";
ResponseEntity<ResponseOrderMessage> responseEntity = restTemplate.postForEntity("/fulfillmentprocessor/orders",
fulfillmentOrderRequestVO, ResponseOrderMessage.class);
ResponseOrderMessage responseOrderMessage = responseEntity.getBody();
assertEquals(HttpStatus.CREATED, responseEntity.getStatusCode());
assertEquals(myMessage, responseOrderMessage.getMessage());
}
lies in src/main/java
#SpringBootApplication
public class StoreOrderFulfillmentApplication {
public static void main(String[] args) {
SpringApplication.run(StoreOrderFulfillmentApplication.class, args);
}
}
Now the problem is I wanted to exclude a class
from being get component scanned.my this class contains the dependency for apache Kafka.
if this class loads while container start up it start looking for kafka running instances.
so while running Intregation test I will not be starting my Kafka server,so I wanted to run
Intregation test making kafka shutdown.
This I can achieved by adding one line code in StoreOrderFulfillmentApplication class
#ComponentScan(basePackages = "com.tesco.store.order.fulfillment.processor", excludeFilters = #Filter(type = FilterType.ASSIGNABLE_TYPE, classes = OrderReceiveEventConfiguration.class))
by addding this line of code StoreOrderFulfillmentApplication class it is excluding OrderReceiveEventConfiguration class from being get component scanned.
now the problem is I not suppose add any test configuration changes in the main code.
so I am struggling to do the same exclusion from src/intregation-test/java source folder, is their some way that I can exclude this particular class during container startup code.
but it should not affect my main class code means code inside src/main/java
Any help is Appreciated..
You can make use of #Conditional as shown below.
In application.properties introduce a property say kafka.enabled.
Annotate the OrderReceiveEventConfiguration with #Conditional(PropertyCondition.class)
Depending on kafka.enabled value viz. true (for normal run) or false (for testing) the OrderReceiveEventConfiguration will be picked up or ignored respectively without changing the code.
Let know in comments in case any more information is required.
Except main #conditional annotation there are set of similar annotation to be used for different cases.
Class conditions
The #ConditionalOnClass and #ConditionalOnMissingClass annotations allows configuration to be included based on the presence or absence of specific classes.
E.g. when OObjectDatabaseTx.class is added to dependencies and there is no OrientWebConfigurer bean we create the configurer.
#Bean
#ConditionalOnWebApplication
#ConditionalOnClass(OObjectDatabaseTx.class)
#ConditionalOnMissingBean(OrientWebConfigurer.class)
public OrientWebConfigurer orientWebConfigurer() {
return new OrientWebConfigurer();
}
Bean conditions
The #ConditionalOnBean and #ConditionalOnMissingBean annotations allow a bean to be included based on the presence or absence of specific beans. You can use the value attribute to specify beans by type, or name to specify beans by name. The search attribute allows you to limit the ApplicationContext hierarchy that should be considered when searching for beans.
See the example above when we check whether there is no defined bean.
Property conditions
The #ConditionalOnProperty annotation allows configuration to be included based on a Spring Environment property. Use the prefix and name attributes to specify the property that should be checked. By default any property that exists and is not equal to false will be matched. You can also create more advanced checks using the havingValue and matchIfMissing attributes.
#ConditionalOnProperty(value='somebean.enabled', matchIfMissing = true, havingValue="yes")
#Bean
public SomeBean someBean(){
}
Resource conditions
The #ConditionalOnResource annotation allows configuration to be included only when a specific resource is present.
#ConditionalOnResource(resources = "classpath:init-db.sql")
Web application conditions
The #ConditionalOnWebApplication and #ConditionalOnNotWebApplication annotations allow configuration to be included depending on whether the application is a 'web application'.
#Configuration
#ConditionalOnWebApplication
public class MyWebMvcAutoConfiguration {...}
SpEL expression conditions
The #ConditionalOnExpression annotation allows configuration to be included based on the result of a SpEL expression.
#ConditionalOnExpression("${rest.security.enabled}==false")
You should able able to create a separate config class in your test package
#SpringBootApplication
#ActiveProfiles("Test")
#ComponentScan(basePackages = "com.tesco.store.order.fulfillment.processor", excludeFilters = #Filter(type = FilterType.ASSIGNABLE_TYPE, classes = OrderReceiveEventConfiguration.class))
public class StoreOrderFulfillmentApplicationTest {
public static void main(String[] args) {
SpringApplication.run(StoreOrderFulfillmentApplicationTest.class, args);
}
}
And then in your test class
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, classes = StoreOrderFulfillmentApplicationTest.class)
#ActiveProfiles("Test")
public class OrderCreationIntregationTest {
#Autowired
private TestRestTemplate restTemplate;
#MockBean
private OrderRepository orderRepository;
#MockBean
private OrderLineItemRepository orderLineItemRepository;
#MockBean
private InternalEventPublisher internalEventPublisher;
#SuppressWarnings("unchecked")
#Before
public void setup() {
Mockito.when(orderRepository.findByOfsReferenceId("OFS:GMO:Z100002062-99")).thenReturn(null);
OrderEntity savedOrder = new OrderEntity();
savedOrder.setOrderId(1023);
Mockito.when(orderRepository.save(Mockito.any(OrderEntity.class))).thenReturn(savedOrder);
Iterable<OrderLineItemEntity> orderLineItemList = prepareOrderLineItemEntityIterable();
Mockito.when(orderLineItemRepository.save(Mockito.any(Iterable.class))).thenReturn(orderLineItemList);
}
#Test
public void test() throws ParseException {
FulfillmentOrder fulfillmentOrderRequestVO = new FulfillmentOrder();
fulfillmentOrderRequestVO = buildFulfillmentOrder();
String myMessage = "Order Created";
ResponseEntity<ResponseOrderMessage> responseEntity = restTemplate.postForEntity("/fulfillmentprocessor/orders",
fulfillmentOrderRequestVO, ResponseOrderMessage.class);
ResponseOrderMessage responseOrderMessage = responseEntity.getBody();
assertEquals(HttpStatus.CREATED, responseEntity.getStatusCode());
assertEquals(myMessage, responseOrderMessage.getMessage());
}
Create a test application class
#SpringBootApplication
#ComponentScan(basePackages = "com.tesco.store.order.fulfillment.processor", excludeFilters = #Filter(type = FilterType.ASSIGNABLE_TYPE, classes = OrderReceiveEventConfiguration.class))
public class TestStoreOrderFulfillmentApplication {
public static void main(String[] args) {
SpringApplication.run(StoreOrderFulfillmentApplication.class, args);
}
}
Add the following configuration annotation to you test class
#SpringApplicationConfiguration(classes = TestStoreOrderFulfillmentApplication .class)