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?
Related
I am trying to write JUnit test cases for an application which is in Java Spring Boot 1.2.5.
I have a service class and I am writing JUnit test cases Using Mockito for the same.
Service Class
class ManualWorkFlowService implements ManualWorkFlowInterface {
#Autowired
private Environment environment;
public void fetchFlowData(Long flowId) {
String config = environment.getProperty("manual.workflow.config");
// Big method body to follow.
}
}
JUnit class
#RunWith(MockitoJUnitRunner.class)
public class LMVendorServiceTest extends BaseTest {
#InjectMocks
private ManualWorkFlowInterface service = new ManualWorkFlowService();
#Test
public void fetchFlowDataTest() throws Exception {
service.fetchFlowData("1234");
}
}
The Junit method calls the fetchFlowData() method, but the environment.getProperty("manual.workflow.config") in original method throws null pointer exception (Because environment property is null)
I have fixed the same like below
#RunWith(MockitoJUnitRunner.class)
public class LMVendorServiceTest extends BaseTest {
#InjectMocks
private ManualWorkFlowInterface service = new ManualWorkFlowService();
#Mock
private Environment mockenvironment;
#Test
public void fetchFlowDataTest() throws Exception {
when(mockenvironment.getProperty("manual.workflow.config")).thenReturn("something");
service.fetchFlowData("1234");
}
}
My doubts are,
Can I configure the same property file which the original
application is using in my Test Class? If yes how?
Is it possible, when I call the original method (fetchFlowData(Long flowId)) from Junit, it automatically takes the application property files configured in original application so that environment.getProperty("manual.workflow.config") works fine without configuring a property file separately in Test Class?
I have tried the below approach but the #Value is not fetching property file value
#RunWith(MockitoJUnitRunner.class)
#PropertySource("file:C:\\properties\\application.properties")
public class LMVendorServiceTest extends BaseTest {
#InjectMocks
private ManualWorkFlowInterface service = new ManualWorkFlowService();
#Mock
private Environment mockenvironment;
#Value("${manual.workflow.config}")
private String property; //Null here
#Test
public void fetchFlowDataTest() throws Exception {
when(mockenvironment.getProperty("manual.workflow.config")).thenReturn(property);
service.fetchFlowData("1234");
}
}
Change:
#Autowired
private Environment environment;
To:
private Environment environment;
#Autowired
void setEnvironment(Environment env) {
// Doesn't need to be public if your service and test are in the same package
this.environment = env;
}
And use Spring's MockEnvironment in your test, adding a PropertySource for whichever properties file you want:
MockEnvironment env = new MockEnvironment();
Resource resource = new ClassPathResource("/my.properties");
Properties props = PropertiesLoaderUtils.loadProperties(resource);
env.getPropertySources().addLast(new PropertiesPropertySource("testProperties", props));
ManualWorkFlowInterface service = new ManualWorkFlowService();
service.setEnvironment(env);
I got 2 modules User and Email, both of them have 1 entry point which is a facade, rest is package scoped. The configuration is done in 2 classes
#Configuration
class UserConfiguration {
#Bean
UserFacade userFacade(UserRepository repository, EmailFacade emailFacade) {
return new UserFacade(repository, emailFacade);
}
}
#Configuration
class EmailConfiguration {
#Bean
EmailFacade emailFacade(EmailSender emailSender) {
return new EmailFacade(emailSender);
}
}
Now, I want to write tests that don't require Spring to start. I implemented a simple InMemoryRepository to make this happen
#RunWith(MockitoJUnitRunner.class)
public class RegisterUserTest {
#Mock
private EmailFacade emailFacade = new EmailFacade(new FakeEmailSender());
#InjectMocks
private UserFacade userFacade = new UserConfiguration().userFacade(new InMemoryUserRepository(), emailFacade);
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
}
I need some fake objects to instantiate EmailFacade so I wrote fake implementation
public class FakeEmailSender implements EmailSender {
#Override
public void sendEmail(EmailMessage emailMessage) throws RuntimeException {
}
}
In that scenario, I'm testing User domain, so I want to mock Email anyways.
I wrote a test to check if it works
#Test
public void shouldReturnSendingFailed() {
Mockito.when(emailFacade.sendUserVerificationEmail(Mockito.any())).thenReturn(Either.left(EmailError.SENDING_FAILED));
assertThat(userFacade.registerNewUser(RegisterUserDto.builder()
.username(USERNAME_4)
.email(VALID_EMAIL)
.password(VALID_PASSWORD).build()).getLeft(), is(EmailError.SENDING_FAILED));
}
But it isn't... after running this test I got
java.util.NoSuchElementException: getLeft() on Right
edit#
regiserNewUser() method
Either<DomainError, SuccessMessage> register(RegisterUserDto registerUserDto) {
if(userRepository.findUser(registerUserDto.getUsername()).isPresent())
return Either.left(UserError.USERNAME_ALREADY_EXISTS);
var userCreationResult = User.createUser(registerUserDto);
var savedUser = userCreationResult.map(this::saveUser);
var emailDto = savedUser.map(this::createVerificationEmail);
return emailDto.isRight() ? emailFacade.sendUserVerificationEmail(emailDto.get())
: Either.left(emailDto.getLeft());
}
Edit2#
With following test configuration
#RunWith(MockitoJUnitRunner.class)
public class RegisterUserTest {
#Mock
private EmailFacade emailFacade;
#InjectMocks
private UserFacade userFacade = new UserConfiguration().userFacade(new InMemoryUserRepository(), emailFacade);
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
}
I got nullpointer here, last line of registerNewUser().
Try running this code
#RunWith(MockitoJUnitRunner.class)
public class RegisterUserTest {
#Mock
private EmailFacade emailFacade;
private UserFacade userFacade;
#Before
public void setUp() {
userFacade = new UserConfiguration().userFacade(new InMemoryUserRepository(), emailFacade);
}
}
There are a few issues with your code:
You initialize your mocks twice. You don’t need to call initMocks in the setUp method if you are using Mockito runner
You are trying to inject mocks to already initialized object. But the field you are trying to inject is also passed to the constructor. Please read #InjectMocks doc, to check the strategies used to inject the mocks:
constructor (not used here, already initialized object)
setter (do you have one?)
field (is it not final)
There are details to each strategy (see my questions above). If no staregy is matched, Mockito will fail silently. The fact that you are passing an object in constructor, and rely on setter or field injection afterwards makes this code unnecesarily complex.
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.
This question already has answers here:
Why is my Spring #Autowired field null?
(21 answers)
Closed 3 years ago.
I've started playing with spring data elasticsearch and have been looking at the example here.
I am having trouble understanding how #Autowired works. Consider the following:
IMessageProcessor.java:
package message.processor;
public interface IMessageProcessor {
void processMessage();
}
MyMessageProcessor.java
package message.processor;
#Component
public class MyMessageProcessor implements IMessageProcessor {
#Autowired
private ArticleServiceImpl articleService;
private final Author johnSmith = new Author("John Smith");
private final Author johnDoe = new Author("John Doe");
#Override
public void processMessage() {
Article article = new Article("Spring Data Elasticsearch");
article.setAuthors(asList(johnSmith, johnDoe));
article.setTags("elasticsearch", "spring data");
articleService.save(article);
}
}
MyMessageProcessorIT.java
package message.processor;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = Config.class)
public class MyMessageProcessorIT {
#Autowired
private ElasticsearchTemplate elasticsearchTemplate;
#Before
public void before() {
elasticsearchTemplate.deleteIndex(Article.class);
elasticsearchTemplate.createIndex(Article.class);
}
#Test
void testProcessMessage() {
MyMessageProcessor msgProcessor = new MyMessageProcessor();
msgProcessor.processMessage();
}
}
Whenever I run the unit test, articleService in MyMessageProcessor is always null. Do I need extra configuration for the autowiring to work? All other code is the same as what is in the github repo linked above.
How do I ensure that wherever in my project I need to use ArticleServiceImpl, it is autowired correctly?
I have seem other posts with the same issue but none of the solutions seem to work for my example below.
You are not using the spring created instance of MyMessageProcessor and creating your own on this line.
MyMessageProcessor msgProcessor = new MyMessageProcessor();
You should autowire MyMessageProcessor in your test class instead.
because of you don't allow to create MyMessageProcessor with spring framework,that because spring doesnt inject your ArticleServiceImpl
#Configuration
public class Configuration {
#Bean
public MyMessageProcessor mymessageProcessor() {
return new MyMessageProcessor();
}
}
then
#Autowire MyMessageProcessor in your test class
#PCL, the first comment link in your post provides the complete answer, but in your code you have 2 choices:
1 - Put MyMessageProcessor msgProcessor autowired:
package message.processor;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = Config.class)
public class MyMessageProcessorIT {
#Autowired
private ElasticsearchTemplate elasticsearchTemplate;
#Autowired
private MyMessageProcessor msgProcessor;
...
#Test
void testProcessMessage() {
msgProcessor.processMessage();
}
}
Or, you get the application context and manually autowire the bean, in the link that was provided by #Tom you can see thus referred to has Manual bean lookup. If you want to create a new bean then the applicationContext also has a autowire method.
Best regards,
David
I'm trying to migrate an old applicaiton step by step, starting with migrating from spring 4.0 to 4.1.
While the documentation says that there are no breaking changes, it seems otherwise.
My integration test looks somewhat like this:
#ContextConfiguration(locations = {"/com/a/KernelTestContext.xml"})
public class KernelTest extends AbstractJUnit4SpringContextTests {
#Configuration
static class ContextConfiguration {
#Bean
...
}
}
#Resource(name = "CsvResourceProcessingChannel")
private MessageChannel csvResourceProcessingChannel;
#Test()
public void testIt() {
...
csvResourceProcessingChannel.send(MessageBuilder.withPayload(new ByteArrayResource(input.getBytes()))
.setHeader(Headers.IMPORT_SOURCE, importSource1).setReplyChannel(testChannel)
.setErrorChannel(testErrorChannel).build());
}
}
The csvResourceProcessingChannel Bean accesses posts into the splitter bean CsvResourceSplitter, which is defined like this:
public class CsvResourceSplitter extends AbstractMessageSplitter {
private final static Logger LOGGER = LoggerFactory.getLogger(CsvResourceSplitter.class);
#Autowired
private ApplicationContext applicationContext;
private Map<Partner, CsvMappingStrategy> csvMappingMap;
...
private void updateCsvMappings() {
final Map<String, CsvMappingStrategy> mappingBeanMap = applicationContext.getBeansOfType(CsvMappingStrategy.class,
false, true);
csvMappingMap = Maps.newEnumMap(Partner.class);
for (final CsvMappingStrategy csvMappingStrategy : mappingBeanMap.values()) {
final CsvMappingStrategy previous = csvMappingMap.put(csvMappingStrategy.getPartner(),
csvMappingStrategy);
Preconditions.checkArgument(previous == null, "More than one CsvMapping bean found for partner: '%s",
csvMappingStrategy.getPartner());
}
}
}
The problem becomes noticeable with the final Preconditions check in updateCsvMappings:
While the test only defines one CsvMappingStrategy class, several more including duplicates are found since the upgrade from spring 4.0 to 4.1.
Due to the contents of mappingBeanMap during runtime I'm pretty sure that the context used here contains context elements used in CsvResourceSplitterTest, which looks somewhat like this:
#ContextConfiguration
public class CsvResourceSplitterTest extends AbstractJUnit4SpringContextTests {
#Configuration
static class Config {
#Bean
public CsvResourceSplitter createCsvResourceSplitter() {
return new CsvResourceSplitter();
}
#Bean
public CsvMappingStrategy createTestMappingStrategy() {
return new AbstractCsvMappingStrategy() {
...
};
}
...
}
#Autowired
private CsvResourceSplitter splitter;
...
}
Any ideas on what is going wrong are appreciated.
A useful workaround for this was moving the CsvResourceSplitterTest context to a different profile:
#ContextConfiguration
#ActiveProfiles("anonymous_profile")
public class CsvResourceSplitterTest extends AbstractJUnit4SpringContextTests {
#Profile("anonymous_profile")
#Configuration
static class Config {
...
}