Providing context for #Value fields from a unit test - java

I have a class like this:
#Service("someClient")
public class SomeClient {
#Value{some.value}
private String someValue;
public void someMethod() {
return someValue;
}
}
And a test like this:
#ContextConfiguration(locations = "classpath:/some/where/testApplicationContext.xml")
#RunWith(SpringJUnit4ClassRunner.class)
public class SomeClientTest extends TestCase {
#Value{some.value}
private String someValueTest;
#Test
public void shouldWork() {
...
someClient.someMethod()
...
}
}
When the wider application is running, the field someValue inside the SomeClient class is populated from a properties file referenced from testApplicationContext.xml. When I run the test in debug mode I can see that someValueTest is populated in the test, but when the test calls the class under test, the value is not populated.
I could use some advice! Obviously I can change the visibility of the field in the class, or provide a setter, however I would like to avoid that if possible. If it isn't, please advise.

In order to populate fields with #Value annotation in your test you need to configure PropertySourcesPlaceholderConfigurer.
Add the following to your test:
#Configuration
public static class Config {
#Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
To read the values from test property file you can add
#TestPropertySource(locations="classpath:test.properties") to your Test class declaration

You can use ReflectionTestUtils from org.springframework.test.util.ReflectionTestUtils package to mock any variable, including the ones that access the properties file.
#RunWith(SpringJUnit4ClassRunner.class)
public class SomeClientTest extends TestCase {
private SomeClient someClient;
#Test
public void shouldWork() {
//Initialize someClient
someClient = new SomeClient();
ReflectionTestUtils.setField(someClient, "variable name", "the variable value");
someClient.someMethod()
...
}
}

Related

Reading custom annotation for JUNIT

I have a custom annotation which I use as config to start off one time set-up for Junit.
#Target(TYPE) #Retention(RUNTIME)
public #interface MyAnnotation{
String host();
int port();
}
Test class:
#MyAnnotation(host="0.0.0.0", port=4567)
public class MyTest extends MyAbstractClass{
#Test
public void myTest(){
//do testy things
}
}
Superclass:
public class MyAbstractClass{
#BeforeAll
public static void start(){
Config cfg = readConfig();
//Use config to do one time set-up
}
private static Config readConfig(){
MyAnnotation ann = MyTest.class.getAnnotation(MyAnnotation.class);
return new Config(ann.host(), ann.port());
}
}
So currently, I hardcode the name of the test class (MyTest) in readConfig(..).
This won't work when I add a second test class.
One way to solve it is:
Add another #BeforeAll method in MyTest which will call the #BeforeAll in super-class and pass the class name as a param.
However, I am curious if I can read the name of the executing subclass in the superclass via some reflexion magic.
Any ideas are most welcome.
Thanks
The presence of the #BeforeAll annotation suggests you are using JUnit 5. In this case, you can use.
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.TestInfo;
public class MyAbstractClass {
#BeforeAll
public static void start(TestInfo ti) {
Config cfg=readConfig(ti.getTestClass().orElseThrow(IllegalStateException::new));
//Use config to do one time set-up
}
private static Config readConfig(Class<?> testClass) {
MyAnnotation ann = testClass.getAnnotation(MyAnnotation.class);
return new Config(ann.host(), ann.port());
}
}
See also the TestInfo API documentation.
This is not “Reflection Magic” but a feature provided by JUnit itself, but it’s also only JUnit which knows that the invocation of a static method annotated with #BeforeAll is associated with a particular test class it is going to process.

TestNG Listener equivalent for #BeforeClass, with access to context

I've spent several days looking for a way to move one of my #BeforeClass methods to listener class I can reference in xml where I define content os test suite.
Problem I'm facing is that I'm using Spring for DI, and in #BeforeClass method I add some attributes to testng context, so I can use them in other places (other listeners).
I tried using onStart(final ITestContext context) from ITestListener. But that method seems to be invoked before spring manages to create beans, and I cannot perform my operations, because all my beans are nulls.
I tried using onBeforeClass(ITestClass testClass) from IClassListener. But that method only provides ITestClass, which does not give me access to context, so I can't set my attributes.
Now I'm experimenting with onConfigurationSuccess(final ITestResult itr) from IConfigurationListener, but that requires using if statement to run my code only if configuration method name is equal to springTestContextPrepareTestInstance.
Does anyone know a better way of doing this?
[EDIT] code sample
#Component
public class CleanupHelper {
private static SomeBean someBean;
#Autowired
public CleanupHelper(SomeBean someBean){
CleanupHelper.someBean = someBean;
}
public static Object getSomething(){
return someBean.getSomething();
}
}
public class ExcludedGroupsListener implements IConfigurationListener {
#Override
public void onConfigurationSuccess(final ITestResult itr) {
if (itr.getName().contains("springTestContextPrepareTestInstance")) {
var something = CleanupHelper.getSomething();
if (something != null && someOtherCondition) {
itr.setAttribute("someObject", something);
}
}
}
}
#ContextConfiguration(classes = TestConfig.class)
public class SomeTests extends AbstractTestNGSpringContextTests {
#Test
public void someTest(){
// doSomething
}
}
#Configuration
#ComponentScan(basePackages = "com.some",
excludeFilters = #Filter(type = FilterType.REGEX, pattern = "com.some.else..*"))
public class TestConfig {
}
Above code works... unfortunately onConfigurationSuccess method is invoked after each configuration method.
Try with Annotation Transformers.
You can add it in your testng.xml like any other listener.
And in there you can do things like:
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import org.testng.IAnnotationTransformer;
import org.testng.annotations.ITestAnnotation;
public class TestAnnotationTransformer implements IAnnotationTransformer {
#SuppressWarnings("rawtypes")
#Override
public void transform(ITestAnnotation annotation, Class testClass, Constructor testConstructor, Method testMethod) {
if (testMethod.getName().equals("MyTest1"))
annotation.setGroups( new String[] {"GroupA" });
if(ignoreTestDependencies)
annotation.setIgnoreMissingDependencies(true);
}
}
Just an example, but you have many things there to play with.
Just bear in mind that, as I stated in the comments, this runs before runtime, so you won't be able to change things on the go like you would do with a normal listener.

How to autowire beans in test class when using #SpringBootTest

I have an integration test class annotated with #SpringBootTest which starts up the full application context and lets me execute my tests. However I am unable to #Autowired beans into the test class itself. Instead I get an error:
No qualifying bean of type 'my.package.MyHelper' available".
If I do not #Autowire my helper class, but keep the code directly inside the setUp function, the test works as expected.
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class)
public class CacheControlTest {
#Autowired
private MyHelper myHelper;
#Before
public void setUp() {
myHelper.doSomeStuff();
}
#Test
public void test1() {
// My test
}
}
How can I make use of Spring autowiring inside the test class while also using #SpringBootTest?
Following #user7294900 advice below, creating a separate #Configuration file and adding this at the top of CacheControlTest works:
#ContextConfiguration(classes = { CacheControlTestConfiguration.class })
However is there any way of keeping the configuration inside the CacheControlTest class itself? I have tried adding inside my test class:
public class CacheControlTest {
#TestConfiguration
static class CacheControlTestConfiguration {
#Bean
public MyHelper myHelper() {
return new MyHelper();
}
}
}
And
public class CacheControlTest {
#Configuration
static class CacheControlTestConfiguration {
#Bean
public MyHelper myHelper() {
return new MyHelper();
}
}
}
But they do not seem to have any effect. I still get the same error. The same configuration block works when placed in an separate file as mentioned above though.
Add ContextConfiguration for your Test Class:
#ContextConfiguration(classes = { CacheControlTestConfiguration.class })

How can I programmatically autowire field which is not part of bean?

In a Spring Boot application I am working on, I have a class which is not annotated as bean (#Component), but contains an autowired field:
public class One{
#Autowired
private Two x;
public getX(){
return x;
}
}
In the configuration xml of the Spring application the class One is marked as bean which makes that the variable x gets initialized when I run the application.
Now I have written a test that doesn't seem to use the spring xml configuration. So I tried to do it manually:
#RunWith(SpringRunner.class)
public class Test{
#Autowired
One y;
#Test
public void checkOne(){
System.out.println(y.getX()); //null
}
}
How can I make Spring inject the correct code so that x is not null in my test?
Just tell the test what config to use:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath:applicationContext_test.xml" })
public class Test{
#Autowired
One y;
#Test
public void checkOne(){
System.out.println(y.getX()); //null
}
}
See here for doc
Alernative to #Essex Boy approach:
use a custom configuration in your test:
#RunWith(SpringRunner.class)
public class TestClass {
#Configuration
#ComponentScan
static class ConfigurationClass {
#Bean
public One makeOne() {
return new One();
}
}
#Autowired
One y;
#Test
public void checkOne(){
System.out.println(y.getX());
}
}
Essex Boy's approach runs an "integration test" because it starts up Spring for the test.
Usually for Unit Tests, you want to mock your depencies; those can be "autowired".
#RunWith(MockitoJUnitRunner.class) // necessary for the annotations to work
public class YourTest {
// this is a mock
#Mock
private Two mockedTwo;
#InjectMocks
// this is automatically created and injected with dependencies
private One sut;
#Test
public void test() {
assertNotNull(sut.getX());
sut.doStuff();
verify(mockedTwo).wasCalled();
}
}

Best practice - Setting a field without setters in a unit test

Let's say you have the following class you would like to test:
public class SomeService {
public String someMethod(SomeEntity someEntity) {
return someEntity.getSomeProperty();
}
}
The SomeEntity looks like this:
public class SomeEntity {
private String someProperty;
public getSomeProperty() {
return this.someProperty;
}
}
The assertion you would like to do can be the following:
String result = someService.someMethod(someEntity);
assertThat(result).isEqualTo("someValue");
How can you make this test work?
1) Add a setter for 'someProperty' in the SomeEntity class. I don't think this a good solution because you don't change production code to make your tests work.
2) Use ReflectionUtils to set the value of this field. Test would look like this:
public class TestClass {
private SomeService someService;
#Test
public void testSomeProperty() {
SomeEntity someEntity = new SomeEntity();
ReflectionTestUtils.setField(someEntity, "someProperty", "someValue");
String result = someService.someMethod(someEntity);
assertThat(result).isEqualTo("someValue");
}
}
3) You create an inner class in your test class that extends the SomeEntity class and adds the setter for this field. However, for this to work you will also need to change the SomeEntity class because the field should become 'protected' instead of 'private'. Test class might look like this:
public class TestClass {
private SomeService someService;
#Test
public void testSomeProperty() {
SomeEntityWithSetters someEntity = new SomeEntityTestWithSetters();
someEntity.setSomeProperty("someValue");
String result = someService.someMethod(someEntity);
assertThat(result).isEqualTo("someValue");
}
public class SomeEntityWithSetters extends SomeEntity {
public setSomeProperty(String someProperty) {
this.someProperty = someProperty;
}
}
}
4) You use Mockito to mock SomeEntity. Seems fine if you only need to mock only one property in the class, but what if you need to mock like 10 properties are so. The test might look like this:
public class TestClass {
private SomeService someService;
#Test
public void testSomeProperty() {
SomeEntity someEntity = mock(SomeEntity.class);
when(someEntity.getSomeProperty()).thenReturn("someValue");
String result = someService.someMethod(someEntity);
assertThat(result).isEqualTo("someValue");
}
}
you can set the value using reflection. It doesn't need any change in production code.
ReflectionTestUtils.setField(YourClass.class, "fieldName", fieldValue);
You can add a setter with default (package private) scope.
With junit testing of SomeService.someMethod()
alternative 1. should not use this as no need to change entity for writing junit.
alternative 2. can be used.
alternative 3. again same a 3, no need to extend for just junit. how about when the class cannot be extended.
alternative 4. yes, a good option. mockito is being used for the same reason.
What is the behavior / contract specific to SomeService that is testable? Based upon your skeletal code, there really isn't any. It will either throw a NPE on bad input, or return a String that may or may not be null, depending on Hibernate magic. Not sure what you can actually test.
I have been through this same dilemma many times before, a quick solution is to make the field you want to mock package protected, or provide a protected setter. Of course both will alter production code.
Alternatively, you can consider dependency injection framework, such as Dagger. Below is an example they give:
#Module
class DripCoffeeModule {
#Provides Heater provideHeater(Executor executor) {
return new CpuHeater(executor);
}
}
This JUnit test overrides DripCoffeeModule's binding for Heater with a mock object from Mockito. The mock gets injected into the CoffeeMaker and also into the test.
public class CoffeeMakerTest {
#Inject CoffeeMaker coffeeMaker;
#Inject Heater heater;
#Before public void setUp() {
ObjectGraph.create(new TestModule()).inject(this);
}
#Module(
includes = DripCoffeeModule.class,
injects = CoffeeMakerTest.class,
overrides = true
)
static class TestModule {
#Provides #Singleton Heater provideHeater() {
return Mockito.mock(Heater.class);
}
}
#Test public void testHeaterIsTurnedOnAndThenOff() {
Mockito.when(heater.isHot()).thenReturn(true);
coffeeMaker.brew();
Mockito.verify(heater, Mockito.times(1)).on();
Mockito.verify(heater, Mockito.times(1)).off();
}
}

Categories