How to inject ANY information about test in spring testing? - java

I would like some of my beans know something about test. SOMETHING. May be test class name or some of it's methods.
For example, suppose my test class has a method
public String getTestName() {
return getClass().getSimpleName();
}
This method returns test name and can be overridden.
Is it possible to inject this name into some beans of Spring context, to use during test?
For example, with autowire feature:
#Autowired
public String testName;
not only in test class, but in other beans too.
UPDATE
Below are two (failed) attempts to implement injecting testInstance. May be there are some convenient ways to do that?
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = TestClassAwareTry._Config.class)
#TestExecutionListeners(value = { TestClassAwareTry._Listener.class },
mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS)
public class TestClassAwareTry {
/**
* Interface to tag beans, who want to know if they are in test
*/
public interface TestInstanceAware {
void setTestInstance(Object value);
}
/**
* Sample bean, which would like to know if it is in test
*/
public static class MyBean implements TestInstanceAware {
private Object testInstance;
{
System.out.println("MyBean constructed");
}
public void setTestInstance(Object value) {
this.testInstance = value;
System.out.println("testInstance set");
}
public Object getTestInstance() {
return testInstance;
}
}
/**
* Attempt to inject testInstance with a bean, implementing {#link BeanPostProcessor}
*/
public static class TestInstanceInjector implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if( bean instanceof TestInstanceAware ) {
TestInstanceAware aware = (TestInstanceAware) bean;
// we don't have access to test instance here
// otherwise I would write
//Object testInstance = getTestInstance();
//aware.setTestInstance(testInstance);
}
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
/**
* Attempt to inject testInstance with test execution listener
*/
public static class _Listener extends AbstractTestExecutionListener {
#Override
public void prepareTestInstance(TestContext testContext) throws Exception {
Object testInstance = testContext.getTestInstance();
ApplicationContext context = testContext.getApplicationContext();
// we don't have setBean() method
// I would write if I have
// context.setBean("testInstance", context);
}
}
/**
* Java-based configuration
*/
#Configuration
public class _Config {
#Bean
public MyBean myBean() {
return new MyBean();
}
#Bean
public TestInstanceInjector testInstanceInjector() {
return new TestInstanceInjector();
// I would acquire test instance here and pass it to constructor, if I can
}
}
#Autowired
public MyBean myBean;
#Test
public void testInjected() {
assertSame( this, myBean.getTestInstance());
}
}

I've ended up creating ContextCustomizerFactory that registers BeanPostProcessor
package com.company.testing.base.spring;
import java.util.List;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.ContextCustomizerFactory;
public class TestAwareContextCustomizerFactory implements ContextCustomizerFactory {
#Override
public ContextCustomizer createContextCustomizer(
Class<?> testClass, List<ContextConfigurationAttributes> configAttributes) {
return (context, mergedConfig) -> {
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.addBeanPostProcessor(
new TestInstanceAwareBeanPostProcessor(mergedConfig.getTestClass()));
};
}
}
TestInstanceAwareBeanPostProcessor
public class TestInstanceAwareBeanPostProcessor implements BeanPostProcessor {
private final Class<?> testClass;
TestInstanceAwareBeanPostProcessor(Class<?> testClass) {
this.testClass = testClass;
}
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof TestClassAware) {
((TestClassAware) bean).setTestClass(testClass);
}
return bean;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
resources/META-INF/spring.factories
# ContextCustomizerFactories for the Spring TestContext Framework
org.springframework.test.context.ContextCustomizerFactory = \
com.company.testing.base.spring.TestAwareContextCustomizerFactory

The only way I've been able to do this is by delaying creation of the subject until you are in the test method and to have the bean in the prototype scope.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { LiveConfig.class, DevConfig.class})
#ActiveProfiles("Dev")
public class MeTest {
#Autowired
public ApplicationContext context;
#Autowired
DevConfig devConfig;
#Rule
public TestName nameRule = new TestName();
#Before
public void setName() {
devConfig.setSettings(nameRule.getMethodName());
}
#Test
public void test() {
Bean subject = context.getBean(Bean.class);
System.out.println(subject.settings);
assertThat(subject.settings, is(nameRule.getMethodName()));
}
#Test
public void test2() {
Bean subject = context.getBean(Bean.class);
System.out.println(subject.settings);
assertThat(subject.settings, is(nameRule.getMethodName()));
}
}
#Configuration
class LiveConfig {
#org.springframework.context.annotation.Bean
public String getSettings() {
return "/some/real/file.txt";
}
#org.springframework.context.annotation.Bean
#Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Bean getBean() {
return new Bean();
}
}
#Configuration
class DevConfig {
private String settings;
#org.springframework.context.annotation.Bean
#Profile("Dev")
#Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public String getSettings() {
return settings;
}
public void setSettings(String settings) {
this.settings = settings;
}
}
class Bean {
public Bean() {
System.out.println("Bean");
}
String settings;
#Autowired
void setSettings(String settings) {
System.out.println("Settings: " + settings);
this.settings = settings;
}
}
This uses Profiles to change what Live sees and what the tests see, and the a NameRule to get the name. It is clunky.
I would NOT use the TestName rule, but rather the TemporaryFolder rule and use that to set whatever setting your application uses for the output folder. I'd also only use DI in a test in very rare cases (i.e. full blown integration tests).

Do you mean like this?
public class MyTest {
#Test
public void testName() {
MyBean b = new MyBean(MyTest.class.getSimpleName());
b.doSomething();
}
}

You can achieve this in a more elegant way using Spring Boot Auto configuration feature by making yours, this way:
define a Configuration class that exposes or registers your bean this way:
#Configuration
public class MyBeanProviderConfiguration {
#ConditionalOnMissingBean
#Bean
public MyBean myBean() {
// return a fully initialised MyBean instance
}
}
Then define a custom annotation Spring Boot like, say #AutoConfigureMyBean this way:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
#ImportAutoConfiguration(MyBeanProviderConfiguration.class)
public #interface AutoConfigureMyBean {}
Then you can use this in your Spring test, here is an example:
#RunWith(SpringRunner.class)
#AutoConfigureMyBean
public class MyTest {
#Autowired
MyBean myBean;
}
Or also declare your MyBean #Autowired dependent bean in a regular Spring test (using a Config class), A MyBean instance will be automatically injected into it.

Related

Spring bean scope for "one object per test method"

I have a test utility for with I need to have a fresh instance per test method (to prevent that state leaks between tests). So far, I was using the scope "prototype", but now I want to be able to wire the utility into another test utility, and the wired instances shall be the same per test.
This appears to be a standard problem, so I was wondering if there is a "test method" scope or something similar?
This is the structure of the test class and test utilities:
#RunWith(SpringRunner.class)
#SpringBootTest
public class MyTest {
#Autowired
private TestDriver driver;
#Autowired
private TestStateProvider state;
// ... state
// ... methods
}
#Component
#Scope("prototype") // not right because MyTest and TestStateProvider get separate instances
public class TestDriver {
// ...
}
#Component
public class TestStateProvider {
#Autowired
private TestDriver driver;
// ...
}
I'm aware that I could use #Scope("singleton") and #DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD) but this refreshes more than I need – a new TestDriver instance for each test would be enough. Also, this approach is error-prone because all tests using the TestDriver would need to know that they also need the #DirtiesContext annotation. So I'm looking for a better solution.
It is actually pretty easy to implement a testMethod scope:
public class TestMethodScope implements Scope {
public static final String NAME = "testMethod";
private Map<String, Object> scopedObjects = new HashMap<>();
private Map<String, Runnable> destructionCallbacks = new HashMap<>();
#Override
public Object get(String name, ObjectFactory<?> objectFactory) {
if (!scopedObjects.containsKey(name)) {
scopedObjects.put(name, objectFactory.getObject());
}
return scopedObjects.get(name);
}
#Override
public void registerDestructionCallback(String name, Runnable callback) {
destructionCallbacks.put(name, callback);
}
#Override
public Object remove(String name) {
throw new UnsupportedOperationException();
}
#Override
public String getConversationId() {
return null;
}
#Override
public Object resolveContextualObject(String key) {
return null;
}
public static class TestExecutionListener implements org.springframework.test.context.TestExecutionListener {
#Override
public void afterTestMethod(TestContext testContext) throws Exception {
ConfigurableApplicationContext applicationContext = (ConfigurableApplicationContext) testContext
.getApplicationContext();
TestMethodScope scope = (TestMethodScope) applicationContext.getBeanFactory().getRegisteredScope(NAME);
scope.destructionCallbacks.values().forEach(callback -> callback.run());
scope.destructionCallbacks.clear();
scope.scopedObjects.clear();
}
}
#Component
public static class ScopeRegistration implements BeanFactoryPostProcessor {
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException {
factory.registerScope(NAME, new TestMethodScope());
}
}
}
Just register the test execution listener, and there will be one instance per test of all #Scope("testMethod") annotated types:
#RunWith(SpringRunner.class)
#SpringBootTest
#TestExecutionListeners(listeners = TestMethodScope.TestExecutionListener.class,
mergeMode = MergeMode.MERGE_WITH_DEFAULTS)
public class MyTest {
#Autowired
// ... types annotated with #Scope("testMethod")
}
I ran into the same problem some time ago and came to this solution:
Use Mocks
I wrote some methods to create specific mockito settings to add behavior to each mock.
So create a TestConfiguration class with following methods and bean definition.
private MockSettings createResetAfterMockSettings() {
return MockReset.withSettings(MockReset.AFTER);
}
private <T> T mockClass(Class<T> classToMock) {
return mock(classToMock, createResetAfterMockSettings());
}
and your bean definition will look like:
#Bean
public TestDriver testDriver() {
return mockClass(TestDriver .class);
}
MockReset.AFTER is used to reset the mock after the test method is run.
And finally add a TestExecutionListeners to your Test class:
#TestExecutionListeners({ResetMocksTestExecutionListener.class})

Parametrize spring configuration

Assuming you have the following spring configuration:
#Configuration
public class Config {
#Bean
public SomeBean someBean() {
SomeBean someBean = new SomeBean();
someBean.setVar("foobar");
return someBean;
}
}
Then I can use this configuration in some other class for example by importing it with #Import(Config.class). Now, say you don't want to hardcode the string "foobar" but pass it as a parameter to that configuration. How would I do that? It would be nice to create a custom annotation like #FooBarConfiguration(var = "foobar"). Is that possible?
The #Ben answer is the classic and better approach. But if you don't want to use a property file, you can use a #Bean for that. Each #Bean holds a value that you would like to inject.
Full code example:
#SpringBootApplication
public class So49053082Application implements CommandLineRunner {
#Bean
String beanValueFooBar() {
return "fooBar";
}
#Bean
String beanValueBarFoo() {
return "barFoo";
}
private class SomeBean {
private String var;
public void setVar(final String var) {
this.var = var;
}
}
#Configuration
public class Config {
#Bean
public SomeBean someBean(String beanValueBarFoo) {
SomeBean someBean = new SomeBean();
System.out.println(beanValueBarFoo);
someBean.setVar(beanValueBarFoo);
return someBean;
}
}
#Override
public void run(String... args) {
}
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(So49053082Application.class, args);
context.close();
}
}
Consider using the #Value annotation:
#Configuration
public class Config {
#Value("${myParamValue}")
public String myParam;
#Bean
public SomeBean someBean() {
SomeBean someBean = new SomeBean();
someBean.setVar(myParam);
return someBean;
}
}
you'll need to put the parameters into the environment somehow: there are various techniques using the OS environment, runtime parameters or configuration files, as suits your purposes.

Full validation test in Spring Boot, injection failing

Hello everyone I wanted to tested the full validation of a Request in my Spring Boot application I mean no testing one validator at a time but all of them on the target object)
First I have my object :
public class UserCreationRequest {
#JsonProperty("profileId")
#NotNull
#ValidProfile
private Integer profileId;
}
Then my Validator (#ValidProfile):
#Component
public class ProfileValidator implements ConstraintValidator<ValidProfile, Integer> {
#Autowired
private IProfileService profileService;
#Autowired
private IUserRestService userRestService;
#Override
public void initialize(ValidProfile constraintAnnotation) {
}
#Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
RestUser restUser = userRestService.getRestUser();
ProfileEntity profileEntity = profileService.getProfile(value, restUser.getAccountId());
return profileEntity != null;
}
}
Now I write my unit test :
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {ValidationTestConfiguration.class})
public class UserCreationRequestValidationTest {
private static LocalValidatorFactoryBean localValidatorFactory;
#Autowired
private IUserService userService;
#Autowired
private IProfileService profileService;
#Autowired
private IUserRestService restService;
#BeforeClass
public static void createValidator() {
localValidatorFactory = new LocalValidatorFactoryBean();
localValidatorFactory.setProviderClass(HibernateValidator.class);
localValidatorFactory.afterPropertiesSet();
}
#AfterClass
public static void close() {
localValidatorFactory.close();
}
#Test
public void validateUserCreationRequestStringfields() {
UserCreationRequest userCreationRequest = new UserCreationRequest();
/* Here fill test object*/
when(userService.getUser(any(Integer.class), any(Integer.class))).thenReturn(new UserEntity());
when(profileService.getProfile(any(Integer.class), any(Integer.class))).thenReturn(new ProfileEntity());
when(restService.getRestUser()).thenReturn(new RestUser());
Set<ConstraintViolation<UserCreationRequest>> violations
= localValidatorFactory.validate(userCreationRequest);
assertEquals(violations.size(), 8);
}
}
and my TestConfiguration is like that :
#Configuration
public class ValidationTestConfiguration {
#Bean
#Primary
public IProfileService profileService() {
return Mockito.mock(IProfileService.class);
}
#Bean
#Primary
public IUserRestService userRestService() { return Mockito.mock(IUserRestService.class); }
}
On execution I can see that in the test itself the injection works :
restService is mapped to "Mock for IUserRestService"
But in my validator it is not injected, userRestService is null.
Same thing for ProfileService
I tried several things seen here, nothing works (code is running, only test conf is failing)
This is because you do not produce the Validator bean so it can be injected.
As you manually instantiate the LocalValidatorFactoryBean, it cannot access to the spring DI defined for this test.
You should produce instead a bean for the Validator, or even reference an existing spring configuration to do so.

How to disable Spring autowiring for a certain bean?

There are some class in jar (external library), that uses Spring internally.
So library class has structure like a:
#Component
public class TestBean {
#Autowired
private TestDependency dependency;
...
}
And library provides API for constructing objects:
public class Library {
public static TestBean createBean() {
ApplicationContext context = new AnnotationConfigApplicationContext(springConfigs);
return context.getBean(TestBean);
}
}
In my application, I have config:
#Configuration
public class TestConfig {
#Bean
public TestBean bean() {
return Library.createBean();
}
}
It's throw en exception: Field dependency in TestBean required a bean of type TestDependency that could not be found..
But Spring should not trying to inject something, because bean is already configured.
Can i disable Spring autowiring for a certain bean?
Based on #Juan's answer, created a helper to wrap a bean not to be autowired:
public static <T> FactoryBean<T> preventAutowire(T bean) {
return new FactoryBean<T>() {
public T getObject() throws Exception {
return bean;
}
public Class<?> getObjectType() {
return bean.getClass();
}
public boolean isSingleton() {
return true;
}
};
}
...
#Bean
static FactoryBean<MyBean> myBean() {
return preventAutowire(new MyBean());
}
This worked for me:
import org.springframework.beans.factory.FactoryBean;
...
#Configuration
public class TestConfig {
#Bean
public FactoryBean<TestBean> bean() {
TestBean bean = Library.createBean();
return new FactoryBean<TestBean>()
{
#Override
public TestBean getObject() throws Exception
{
return bean;
}
#Override
public Class<?> getObjectType()
{
return TestBean.class;
}
#Override
public boolean isSingleton()
{
return true;
}
};
}
}
It seems like it's impossible to disable autowiring for a specific bean.
So there is some workaround.
We can make wrapper for a target bean and use it instead of original bean:
public class TestBeanWrapper {
private final TestBean bean;
public TestBeanWrapper(TestBean bean) {
this.bean = bean;
}
public TestBean bean() {
return bean;
}
}
#Configuration
public class TestConfig {
#Bean
public TestBeanWrapper bean() {
return new TestBeanWrapper(Library.createBean());
}
}
#RestController
public class TestController {
#Autowired
private TestBeanWrapper bean;
...
}
Not exactly but you can add required=false (#Autowired(required=false)) in your autowired annotation. But be careful that might get you NullPointer exception

FactoryBean doesn't use when bean instantiates

I am investigating how does FactoryBean works in spring framework.
As I understand it allow configure instantiation process.
I have the following beans:
#Component
public class MyInjectionClass {
String name;
Integer age;
//get and set methods
}
and
#Component
public class MyComponent {
#Autowired
MyInjectionClass myInjectionClass;
public MyInjectionClass getMyInjectionClass() {
return myInjectionClass;
}
}
and following cutom FactoryBean:
#Component
public class MyInjectionClassFactoryBean implements FactoryBean<MyInjectionClass> {
#Override
public MyInjectionClass getObject() throws Exception {
MyInjectionClass myInjectionClass = new MyInjectionClass();
myInjectionClass.setName("name");
myInjectionClass.setAge(12);
return myInjectionClass;
}
#Override
public Class<?> getObjectType() {
return MyInjectionClass.class;
}
#Override
public boolean isSingleton() {
return false;
}
}
Also I have wrote following code in my main method:
MyComponent bean = context.getBean(MyComponent.class);
System.out.println(bean.getMyInjectionClass().getAge());
It returns null.
What did I forget to do ?
P.S.
I use #ComponentScan("com.example.domain")
All beans and FactoryBean located there.
Solution
remove #Component above MyInjectionClass

Categories

Resources