How to access Spring #Service object from jUnit test - java

Situation: I have service implementation class annotated with #Service with access to properties file.
#Service("myService")
public class MySystemServiceImpl implements SystemService{
#Resource
private Properties appProperties;
}
Properties object is configured through config-file. applicationContext.xml
<util:properties id="appProperties" location="classpath:application.properties"/>
I want to test some methods of this implementation.
Question:How to access MySystemServiceImpl-object from test class in such way that Properties appProperties will be initialized properly?
public class MySystemServiceImplTest {
//HOW TO INITIALIZE PROPERLY THROUGH SPRING?
MySystemServiceImpl testSubject;
#Test
public void methodToTest(){
Assert.assertNotNull(testSubject.methodToTest());
}
}
I can't simple create new MySystemServiceImpl - than methods that use appProperties throws NullPointerException. And I can't directly inject properties in the object - there is no appropriate setter-method.
Just put correct steps here (thanks to #NimChimpsky for answer):
I copied application.properties under test/resources dir.
I copied applicationContext.xml under test/resources dir. In application context I add new bean (definition of application properties is already here):
<bean id="testSubject" class="com.package.MySystemServiceImpl">
I modified test class in such way:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations={"/applicationContext.xml"})
public class MySystemServiceImplTest {
#Autowired
MySystemServiceImpl testSubject;
}
And this make the trick - now in my test class fully functional object is available

Alternatively, to do an integration test I do this.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations={"/applicationContext-test.xml"})
#Transactional
public class MyTest {
#Resource(name="myService")
public IMyService myService;
Then use the service as you would normally. Add the app context to your test/resources directory

Just use its constructor :
MySystemServiceImpl testSubject = new MySystemServiceImpl();
This is a unit test. A unit test tests a class in isolation from the other classes and from the infrastructure.
If your class has dependencies over other interfaces, mock those interfaces and create the object with these mocks as argument. That's the whole point of dependency injection : being able to inject other, mock implementations inside an object in order to test this object easily.
EDIT:
You should provide a setter for your properties object, in order to be able to inject the properties you want for each of the unit tests. The injected properties could contain nominal values, extreme values, or incorrect values depending on what you want to test. Field injection is practical, but doesn't fit well with unit-testing. Constructor or setter injection should be preferred when unit-testing is used, because the main goal of dependency injection is precisely to be able to inject mock or specific dependencies in unit tests.

Related

Spring Boot - how to initialize class from a dependent module with injected dependencies

I have two Maven projects A and B, where project B is nested in project A. The structure looks like the following:
Project A:
src/test/java:
MyTest.java
Project B:
src/test/java:
MyNewTest.java
pom.xml
pom.xml
My goal is to let MyNewTest.java act as a wrapper of MyTest.java, and be able to invoke the test methods declared in MyTest.java from MyNewTest.java. MyTest has some injected dependencies.
My question is: how to initialize MyTest in MyNewTest to make sure that all the dependencies of MyTest are injected properly?
MyTest looks like the following:
public class MyTest {
#Autowired
Service service;
#Autowired
TestUtil util;
Info info;
#PostConstruct
void init() {
info = service.getStuff();
}
#Test
public test1() {
service.getMoreStuff();
// more code omitted
}
}
I have tried adding #Component to MyTest, and then in MyNewTest.java, use #Autowired like the following:
public class MyNewTest {
#Autowired
private MyTest baseTest;
#Test
public void runTest() {
// run the test in MyTest.java
baseTest.test1();
}
}
But this doesn't seem to work - baseTest is null. I also tried initializing MyTest by calling its default constructor, but the dependencies failed to be injected as expected. Can anyone suggest a proper way to initialize MyTest.java so that all its dependencies are injected as well? Thanks
The problem with your test is the #Autowired annotations as the Spring wiring isn't being triggered. There are two different paths you can take to fix this.
The first option is to manually instantiate baseTest in MyNewTest at which point it will no longer be null. However, tests will still fail because your two #Autowired dependencies will be null. You can either add setters or inject them via your constructor. Note- these classes should be mocked if you are performing Unit Tests. Here's how it would look if you chose to add these classes via the constructor -
private Service service;
private TestUtil util;
private MyTest baseTest;
#BeforeEach
void setUp() {
service = mock(Service.class);
util = mock(TestUtil.class);
baseTest = new MyTest(service, util);
}
The second option is to add configuration to your Test class to support the Spring wiring. I am not familiar with taking this route as I always choose the first option when possible. There are multiple ways to add the Spring wiring but the easiest is-
#SpringBootTest
#RunWith(SpringRunner.class)
public class MyNewTest {
However, this does not cover all use cases so you may need more specific configurations. You can find some of the possible options here - How to write JUnit test with Spring Autowire?
Edit- As I immediately recognized a problem, I didn't read the rest very carefully and I missed that these were Test classes you were trying to wire together. I am not sure if this is possible.
Only Spring beans can be #Autowired so the first step would be to attempt to add configuration to make your Test class into a Spring bean. I've never heard of this being done before but it would be easy to try. If not, you can get around this problem by using the first option.
The second problem is that tests are not included in the artifact. I'd imagine you could circumvent this issue by mixing your Test classes in with your regular classes but this is considered a bad practice. I've never heard of tests being dependent on other tests but I'd guess this is also a bad practice. What's the reason for wanting to create your Tests this way?

How can I inject property source of a bean in test

I am writing unit tests for my services in Spring, Java. I mock all dependencies in a tested class and I instantiate the tested class in a constructor, to which I pass mocked classes. The problem is that the tested class injects properties from .properties file into fields that are inside it (let's say Strings).
I use the standard combination of #PropertySource on a class level and #Value on a field level inside my tested class.
As we know, properties injection fails when class is instantiated through constructor (not as a bean during the Spring Container initialization). How do you deal with such problem?
I've got one solution, though I think it is bad and unsatisfactory, that is:
1. to #Autowire the class under test normally, then replace all its dependencies by using a setter.
I also know about the #TestPropertySource annotation and if I understand correctly, it does not provide a solution and it is only a way to override already existent properties - which is not the case, as we cannot really use any properties.
Thanks for help in advance :)
It is rather straight : in your unit test, inject the property in a String field and create the object under test not in the constructor of the test class but in the hook method invoked after the container has loaded the Spring context.
In JUnit 4, you specify this hook method with #Before and in JUnit 5 with #BeforeEach.
It would give something like :
#RunWith(SpringJUnit4ClassRunner.class)
public class FooTest{
Foo foo;
#Value("${myProp}")
String myProp;
#BeforeEach
public void beforeEach(){
foo = new Foo(myProp);
}
}
Note that to make your test be executed faster you should load from the Spring context only what your test requires : the environment part.

How can I test only one bean using existing application's spring configuration class?

In my code, I don't want to load all the beans defined in the XXApplicationConfig class.
XXApplicationConfig is a #Configuration annotated file which has bunch of spring beans defined.
So, I want to load only AppBean from XXApplicationConfig class while testing to reduce loading test time and also differentiate what I am testing. I also want to load the class using XXApplicationConfig class to make sure the bean configuration defined is correct as well.
This is my Test class ( modified ) to test AppBean class.
Could you let me know if this is the right approach and suggest how to make it better? Currently, this approach seems to be working. But, not sure if it is correct way of approaching it.
#ContextConfiguration(loader=AnnotationConfigContextLoader.class)
#RunWith(SpringJUnit4ClassRunner.class)
public class ApplicationTest {
#Configuration
#PropertySources(value = {#PropertySource("classpath:test.properties")})
static class MyTestConfiguration {
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceHolderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
#Bean
public XXApplicationConfig xxAppConfig() {
return new XXApplicationConfig();
}
#Bean
public CustomTestService customTestService() {
return new CustomTestService();
}
#Bean
public AppBean appBean() throws Exception {
return XXApplicationConfig().appBean();
}
}
#Autowired
private AppBean appBean;
#Test
public void testAppBean() {
test appBean.doSomething();
}
}
If you want to test just one object, just create one object of that class, using the constructor of that class. Spring beans are designed to be POJOs. The Spring context is just a convenient way of creating and connecting objects. Nothing stops you creating and connecting them yourself.
If you can instantiated the class you want to test and manually inject all the dependencies it required via constructor and/or setter getters, then you don't need to use Spring in your test.
However, if your bean:
uses private fields annotated with #Autowired or #Value without corresponding getters/setters.
depends on many other beans.
the behavior you want to test depends on Spring AOP/Proxies (you use #Transactional or #Cacheable for example).
Then you will need Spring to wired the bean. I personally prefer to define a a minimal #Configuration class for these cases.
Then again, if your bean meets the conditions on the list you should consider refactoring the bean to minimize its dependencies and facilitate testing.

Spring / Mock components that are retrieved by using the context directly

We have a project that uses Spring annotations to configure its context.
To test this project we are using Mockito and it's Spring extension.
In tests I need to override some beans with mock/spy version.
With the #Mock/#Spy and #InjectMock annotations I have been able to use spy for beans using autowiring mechanism.
Now I have a third party component which create another Spring context and then merge the 2 contexts together. This third party component retrieve beans using a call to the context:
applicationContext.getBean(key);
In this case, the #Mock/#Spy and #InjectMock combination is not working.
The 'workaround' solution I have put in place is an XML file in which I declare my spied bean:
<mockito:spy beanName="beanToSpy"/>
To stay in the annotation world, I have tried springockito-annotations as mentioned in these similar questions:
Injecting Mockito mocks into a Spring bean
and its duplicate:
How to inject a Mock in a Spring Context
But the bean is not spied or mocked, I've probably a configuration error.
My current setup is:
A base class that is in charge of the Spring config for test:
#RunWith(SpringJUnit4ClassRunner.class)
#ActiveProfiles("test")
#ContextConfiguration(loader= SpringockitoContextLoader.class, locations ={"/config.xml","/test-config.xml"})
public abstract class BaseTest {
//...
}
A test class that would like to use a mocked bean:
public class MyTest extends BaseTest {
#ReplaceWithMock #Autowired MyService myService;
#WrapWithSpy #Autowired OtherService otherService;
#Test public void someTest() {
doReturn(x).when(myService).call();
doReturn(y).when(otherService).process();
}
}
Unfortunately in MyTest, the beans are not replaced by their mock/spy versions, it is the plain old regular version.
EDIT:
The third party component that does the lookup is using its own spring parent context and add the current spring context into its own. The lookup to retrieve the component that I want to be mocked occurs after the context has been fully loaded.
What should I do to properly replace the bean in the context with a mock/spy version ?
What is wrong with the way I'm using #WrapWithSpy / #ReplaceWithMock ?
When does the call to
applicationContext.getBean(key);
happens? Is it possible that it retrieves the bean before the SpringockitoContextLoader has a chance to replace it with a spy?
One solution to stay in annotation world would be to override the bean in java config:
#Bean
public MyBeanType myBeanType() {
return spy(new MyBeanType(...))
}
The more conventional way to perform this is by simply mocking them in the test as required :
public class MyTest extends BaseTest {
#Test public void someTest() {
MyService myMockService = Mockito.mock(MyService.class);
// define when's
// perform verification
}
You can inject using SpringReflectionTestUtils, or if using setter injection just set normally.
If you use a global mocked bean in a test class with multiple tests, the results can get confusing.

How to autowire object to bean if class does not have setter

I have so controller
#Controller
public class MyController {
#Autowire
MyClass myClass;
//myClass doesn't have setter and getter
....
#RequestMapping("/path")
public String underTest(){
myClass.makeSomething();
return "html.jsp"
}
I want make mock test using Mockito and mock myClass.
In test class I want get myClass so:
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/BeanConfig.xml");
myClass = context.getBean("myClass ", MyClass .class);
But I need autowire this bean to Controller for testing controller's method(I think test code should not affect to normal code).
There are exist way to make it without writing of set method?
I want to check that myClass.makeSomething() invokes once in method underTest.
As long as your test for MyController resides in the same package as MyController itself (as it's usually done - same packages in different source folders), you can simply assign it:
MyController controller = new MyController();
controller.myClass = mockMyClass;
That's the reason not to put #Inject/#Autowired on private fields.
I'm not sure I agree with you that test code should not affect normal code. I think an entirely valid reason to refactor / rewrite production code is to make it more testable (this is probably achieved by making it more modular, which is generally a good thing anyway).
This is precisely why annotations like
#VisibleForTesting
exist. Then you can create a package-local setter for MyClass, add the above annotation (for information to other programmers and possibly code inspection tools) and set the field in your test (which should reside in the same package).
Alternatively, since you are using Mockito, you could simply annotate the MyController instance with #InjectMocks, eg
#Test
public class MyControllerTest {
#Mock
private MyClass mockMyClass;
#InjectMocks
private MyController myController;
#BeforeMethod
public void before() {
MockitoAnnotations.initMocks(this);
}
// do tests...
}
Note that #InjectMocks does not depend on any annotations on the target field (i.e. #Autowired, #Resource, #Inject etc). It just works. (Presumably you will still need those annotations for Spring injection, so don't remove them! The point is you can also use it for fields that aren't annotated).
Note also that, depending on which version of Mockito you are using, you may need to instantiate the MyController in the before() method before calling MockitoAnnotations.initMocks()
Try testing the controller directly with context.getBean(). MyClass will be autowired into it.
I agree with #axtavt's answer, however if you absolutely want to go your way with injecting the mock in an integration test, you can do this:
define a overriding bean configuration file, say bean-test-config.xml, with content along these lines:
<import resource="classpath:spring/BeanConfig.xml"/>
<bean name="myClass" factory-method="mock" class="org.mockito.Mockito">
<constructor-arg value="MyClass"></constructor-arg>
</bean>
This should correctly inject in a mock in your controller. You will have to get hold of this mock in your test and inject in any behavior that you are expecting from this mock though.

Categories