I have an object that looks like this:
#Service
#ConditionalOnProperty(
name="name",
havingValue = "true"
)
public class MyClass{
#Autowired(required = false)
private SomeObject someObject;
}
And I have this in my test file:
#ExtendWith({SpringExtension.class})
#ContextConfiguration(classes = {MyClass.class}, loader = AnnotationConfigContextLoader.class)
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class MyTest {
MyClass myClass;
#Autowired
#ConditionalOnProperty(
name = "name",
havingValue = "true"
)
static MyClass getMyClass(){
return new MyClass();
}
}
#MockBean
static SomeObject someObject;
#BeforeAll
public void before() {
myClass = MyTest.getMyClass()
when(someObject.someFunc()).thenReturn(1);
}
}
I'm using this function because just using #Autowired and #MockBean on the SomeObject didn't work because of the #ConditionalOnProperty.
The problem is that inside MyClass, someObject is null.
No need to use static MyClass getMyClass()
use:
#ExtendWith({SpringExtension.class})
#ContextConfiguration(classes = {MyClass.class, SomeObject.class, loader = AnnotationConfigContextLoader.class)
#SpringBootTest(properties = "name=true")
public class MyTest{
#Autowired
MyClass
#Autowired myClass;
SomeObject someObject;
}
Related
I have a configuration class that uses a properties file and it works properly.
Now I want to test that code and I have to recognize that the method annotated with #PostConstruct is run twice during the test. (In debug mode I can see that the for-loop is conducted twice.)
The configuration class:
#Slf4j
#RequiredArgsConstructor
#Configuration
#ConfigurationPropertiesScan("com.foo.bar")
public class MyConfig {
private final MyProperties myProperties;
#Autowired
private GenericApplicationContext applicationContext;
#PostConstruct
void init() {
Objects.requireNonNull(myProperties, "myProperties may not be null");
for (final MyProperties.MyNestedProperty nested : myProperties.getApps()) {
log.info("xxx {} created.", nested.getName());
applicationContext.registerBean(nested.getName(), MyContributor.class, nested);
}
}
}
The used properties class:
#Slf4j
#Data
#Validated
#ConfigurationProperties(prefix = MyProperties.CONFIG_PREFIX)
public class MyProperties {
public static final String CONFIG_PREFIX = "xxx";
#Valid
#NestedConfigurationProperty
private List<MyNestedProperty> apps;
#Data
public static class MyNestedProperty {
#NotNull
#NotEmpty
private String abc;
private String xyzzy;
#NotNull
#NotEmpty
private String name;
}
}
My attempt with the test class:
#ExtendWith(SpringExtension.class)
#RequiredArgsConstructor
#ContextConfiguration(classes = MyConfigTest.MyTestConfiguration.class)
class MyConfigTest {
#MockBean
MyProperties myProperties;
ApplicationContextRunner context;
#BeforeEach
void init() {
context = new ApplicationContextRunner()
.withBean(MyProperties.class)
.withUserConfiguration(MyConfig.class)
;
}
#Test
void should_check_presence_of_myConfig() {
context.run(it -> {
assertThat(it).hasSingleBean(MyConfig.class);
});
}
// #Configuration
#SpringBootConfiguration
// #TestConfiguration
static class MyTestConfiguration {
#Bean
MyProperties myProperties() {
MyProperties myProperties = new MyProperties();
MyProperties.MyNestedProperty nested = new MyProperties.MyNestedProperty();
nested.setName("xxx");
nested.setAbc("abc");
nested.setXyz("xyz");
myProperties.setApps(List.of(nested));
return myProperties;
}
}
}
Why does this happen and how can I prevent this behaviour?
I have following class (partial code):
#Component
class TestClass: InitializingBean, DisposableBean {
#Autowired
private lateinit var testBean: SomeObject
override fun afterPropertiesSet(){
log.info("testBean 1: $testBdean")
}
fun testFunction(testName: String): Boolean {
log.info("testBean 2: $testBdean")
}
#Throws(Exception::class)
override fun destroy() {
}
}
I saw testBean 1 was run successfully but testBean 2 gave error: lateinit property testBean has not been initialized. So the testBean bean was initialized in afterPropertiesSet() and not available in other functions? I know if we put testBean in the constructor TestClass(testBean) it will be initialized and available to all functions. But is there another way because TestClass will be called from other packages and not every package can pass the testBean to the constructor.
You could create an object that holds your TestClass and use that holder to refer to your create component
something like:
#SpringBootApplication
class DemoApplication
fun main(args: Array<String>) {
runApplication<DemoApplication>(*args)
}
#Component
class SomeObject(val name:String = "some object")
#Component
class TestClass(val someObject: SomeObject) {
init {
TestClassHolder.testClass = this
}
fun test() = someObject.name
}
object TestClassHolder {
lateinit var testClass: TestClass
}
class NotBeanClass {
fun call() = TestClassHolder.testClass.test()
}
#RestController
class TestController {
#GetMapping
fun test() = NotBeanClass().call()
}
In my JUNIT5-test I want to mock a bean by #MockBean. In my #BeforeEach - method the calls are injected.
But other beans #Autowire-ing the #MockBean are instantiated with the #MockBean before the method injection. This is weird and gives me NPEs. How can I force method injection before use of the #MockBean?
#SpringBootTest
#ExtendWith(SpringExtension.class)
#ContextConfiguration("classpath:context/authenticationStaff.xml")
#EnableAutoConfiguration
public class PasswordPolicyServiceTest {
private final List<Reference> bcryptDigestRefs = new ArrayList<>();
private final DigestHistoryRule bcryptDigestRule = new DigestHistoryRule(new BCryptHashBean());
#MockBean
private SystemConfiguration systemConfiguration;
#BeforeEach
public void initMock() {
MockitoAnnotations.initMocks(this);
Arrays.asList(SystemConfigKey.values()).forEach(key -> {
Mockito.when(systemConfiguration.getConfig(key)).thenReturn(getConfig(key, key.getDefaultValue()));
});
Mockito.when(systemConfiguration.getConfig(SystemConfigKey.MIN_PASSWORD_LENGTH)).thenReturn(getConfig(SystemConfigKey.MIN_PASSWORD_LENGTH, "5"));
A failing class is:
#Service
public class SessionCacheManager {
private final Ehcache ehCache;
private final Cache<String, SessionVerificationType> sessionCache;
private final SystemConfiguration systemConfiguration;
#Autowired
public SessionCacheManager(final Ehcache ehCache, final SystemConfiguration systemConfiguration) {
this.ehCache=ehCache;
this.systemConfiguration=systemConfiguration;
SystemConfigType systemConfig = systemConfiguration.getConfig(SystemConfigKey.SESSION_MAX_NUMBER);
Integer numberOfParalledSessions = systemConfig.getIntegerValue();
CacheManager cacheManager=ehCache.registerNewCacheManager(CACHE_MANAGER);
sessionCache = cacheManager.createCache(CACHE_NAME,
CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, SessionVerificationType.class, ResourcePoolsBuilder.heap(numberOfParalledSessions)));
}
As I can see (with debug), the "SessionCacheManager" uses the mocked "SystemConfiguration" but systemConfiguration.getConfig(SystemConfigKey.SESSION_MAX_NUMBER); returns a null.
I helped myself, though I do not like my solution. It is more a trick than a solution. But I cannot think of something else right now.
I change the #ContextConfiguration to:
#ContextConfiguration(locations = "/context/authenticationStaff.xml", classes = { SpringApplicationContext.class })
The XML is setup, that it cannot autodetect the class "SystemConfiguration.class".
Instead of that the "SpringApplicationContext.class" provides the "SystemConfiguration.class" as a mocked bean.
#Configuration
public class SpringApplicationContext {
#Mock
private SystemConfiguration mockedSystemConfiguration;
#Bean
public SystemConfiguration systemConfiguration() {
MockitoAnnotations.initMocks(this);
Arrays.asList(SystemConfigKey.values()).forEach(key -> {
Mockito.when(mockedSystemConfiguration.getConfig(key)).thenReturn(getConfig(key, key.getDefaultValue()));
});
Mockito.when(mockedSystemConfiguration.getConfig(SystemConfigKey.MIN_PASSWORD_LENGTH)).thenReturn(getConfig(SystemConfigKey.MIN_PASSWORD_LENGTH, "5"));
Mockito.when(mockedSystemConfiguration.getConfig(SystemConfigKey.PASSWORD_BCRYPTENCODER_COSTFACTOR)).thenReturn(getConfig(SystemConfigKey.PASSWORD_BCRYPTENCODER_COSTFACTOR, "5"));
return mockedSystemConfiguration;
}
private SystemConfigType getConfig(SystemConfigKey key, String value) {
SystemConfigType config = new SystemConfigType();
config.setKey(key);
config.setValue(value);
return config;
}
The test-code now looks like this:
#SpringBootTest
#ExtendWith(SpringExtension.class)
#ContextConfiguration(locations = "/context/authenticationStaff.xml", classes = { SpringApplicationContext.class })
#EnableAutoConfiguration
public class PasswordPolicyServiceTest {
#Autowired
private PasswordPolicyService passwordPolicyService;
#Autowired
private PasswordHandlerService passwordHandlerService;
#Autowired
private SystemConfiguration systemConfiguration;
private final List<Reference> bcryptDigestRefs = new ArrayList<>();
private final DigestHistoryRule bcryptDigestRule = new DigestHistoryRule(new BCryptHashBean());
#BeforeEach
public void initMock() {
MockitoAnnotations.initMocks(this);
String password=passwordHandlerService.getPassword("my$Password");
bcryptDigestRefs.add(
new HistoricalReference(
"bcrypt-history",
password));
}
This works, but is not a nice solution. Other recommendations are very welcome.
I have my main configuration
#EnableScheduling
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = { "gr.citystore.web.helios.yeastar" })
#PropertySource(value = { "classpath:application.properties" })
public class HelloWorldConfiguration extends WebMvcConfigurerAdapter {
#Autowired
private MyServiceImpl myService;
#EventListener(ContextRefreshedEvent.class)
public void contextRefreshedEvent() {
MyThread mThread = new MyThread(myService);
}
}
And my service the code is:
#Service("myService")
#PropertySource(value = { "classpath:application.properties" })
public class MyServiceImpl implements MyService{
#Value("${property.api.ip}")
private String apiIP;
#Value("${property.api.port}")
private String apiPort;
public String myMethod() {
}
}
My problem is that the #Value annotation is not working when I pass it as argument in myThread, instead is return "${property.api.port}".
What I am missing here?
EDIT:
The application.properties file location is "src/main/resources" and the content is:
property.api.ip = 12.34.50.30
property.api.port = 50034
You could just inject the values of the application.properties into your class like this:
#Service("myService")
public class MyServiceImpl implements MyService{
private final String apiIP;
private final String apiPort;
public MyServiceImpl(#Value("${property.api.ip}") String apiIP,
#Value("${property.api.port}") apiPort) {
this.apiIP = apiIP;
this.apiPort = apiPort;
}
public String myMethod() {
}
}
My Service class uses a property set it application.properties
#Service
public class Service {
#Value("${my.prop}")
private String prop;
public Response doWork(String param1,String param2){
System.out.println(prop); //NULL!!!
}
}
I want to test it and set my own value:
Test Class:
#RunWith(MockitoJUnitRunner.class)
#TestPropertySource(locations = "application.properties",properties = { "my.prop=boo" })
public class ServiceUnitTest {
#InjectMocks
private Service service;
#Test
public void fooTest(){
Response re = service.doWork("boo", "foo");
}
}
But when I run the test, the value is null (not even the value that exists in application.properties).
I don't have experience with MockitoJUnitRunner, but I am able to achieve this using PowerMockRunner.
Set up the application context with a test configuration, and autowire the bean from you application context into your test class. You also shouldn't need the "locations = " bit in the #TestPropertySource annotation.
#RunWith(PowerMockRunner.class)
#PowerMockRunnerDelegate(SpringRunner.class)
#TestPropertySource(properties = { "my.prop=boo" })
public class ServiceUnitTest {
#TestConfiguration
static class ServiceUnitTestConfig {
#Bean
public Service service() {
return new Service();
}
}
#Autowired
Service service;
#Test
public void fooTest(){
Response re = service.doWork("boo", "foo");
}
}