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.
Related
I'm developing a MessageHandler class which contains a Cache field which is also I developed, MessageHandler is annotated as a #Component and Cache is not bean.
When using Spring-boot-test to test the MessageHandler class, I use #Mockbean to mock a MessageHandler and try to use setter to set a Cache object for the mock bean. But when I run test to see if MessageHandler has a cache, I got null.
My code is as follows:
#MockBean(value = HiMessageHandler.class)
MessageHandler messageHandler;
public void setUp(){
Cache cache=new Cache();
cache.offer(new HiMessage("1","1","1"));
messageHandler.setCache(cache);
}
#Test
public void testConsume() throws Exception {
setUp();
System.out.println(messageHandler.getCache());
}
I want to know how to set a field that is not a bean for a mock bean.
I think the main issue here is that you try to mock the class that you test.
You test MessageHandler class in an integration test (otherwise, if its a unit test - then spring boot test is not required)
So the goal is to check that the code that you wrote in MessageHandler really works, and since its an integration test, probable with an emphasis on interaction with other real/semi-real components.
Side note, if you're not sure about the difference between unit and integration tests feel free to read my answer on this question or just google, it will provide more valuable resources for sure, the point is you should clearly understand the difference between the two and pick the right tool for the job.
On the other hand, when you use #MockBean annotation, Spring creates a Mock for your bean, and puts this mock instead of the real bean into application context, so that other beans, if dependent on this message handler, could get the reference on this mock instead of the reference on the real bean.
Then you call some method on a mock (setCache) and indeed it doesn't work, because mock is not a real thing, its a mock, the correct way to interact with mock is to specify interaction rules (style when(...).thenReturn(...) in mockito syntax). Yes, it doesn't work, so this is a direct answer on your question, but as I've explained above the issue is deeper here IMHO.
I am new to spring concepts. I am trying to write a Junit for one of the class's method. I want to mock spring framework JdbcTemplate class that is being used as #Autowired from multiple location. But I don't want to use #Mock in each class. Is there any way I can mock this class and that can be used from multiple location by using #Autowired.
Thanks in Advance.
Define a spring profile specific for your tests (as example "test"), in that profile create the JdbcTemplate bean with a mock library and with the annotation #Profile("test"). In that way this mock will be created only when the "test" profile is active.
Add the annotation #Profile("!test") to the real JdbcTemplate bean so it will not be created in the test profile.
Then add the #ActiveProfiles("test") annotation on top of you test class.
Kind of a non-answer, but I would not recommend using Spring to inject dependencies when unit testing. If you are using constructor injection, then you can simply call the constructor from the unit test and pass in your mock or test double manually. This makes it very clear and explicit from the unit test what the supplied dependency is for the purposes of the test. If you are using setter injection, you can do the same. Don't materialize the class under test via Spring, just construct the class under test from your junit test class, and supply the dependencies you want on that class imperatively.
A simple question that might have an advanced answer.
The Question:
My question is, is there a way to instantiate only the classes, in your application context, needed for that specific JUnit test ?
The Reason:
My application context is getting quite big. I also do a lot of integration tests so you I guess you would understand when I say that every time I run a test all the classes in my application context get instantiated and this takes time.
The Example:
Say class Foo inject only bar
public class Foo {
#Inject
Bar bar;
#Test
public void testrunSomeMethod() throws RegisterFault {
bar.runSomeMethod();
}
but the application context has beans foobar and bar. I know this is not a vaild application context but rest assure all my code works.
<beans>
<bean id="foobar" class="some.package.FooBar"/>
<bean id="bar" class="some.package.Bar"/>
<beans>
So how do I tell spring to only instantiate Bar and ignore FooBar for the test class foo.
Thank you.
Consider adding default-lazy-init="true" to your spring context xml beans tag (or add lazy-init="true" to those specific beans that take a long time starting up).
This will ensure that only those beans are created that called with applicationContext.getBean(class-or-bean-name) or injected via #Autowired / #Inject into your tests. (Some other types of beans like #Scheduled beans will be created nevertheless but you need to check if that's a problem or not)
(if you use spring Java configuration, add #Lazy to the config files)
Caveat - If there is a bean that is not initialized explicitly with applicationContext.getBean() or injected as a dependency used by the bean obtained by using applicationContext.getBean(), then that bean will NO LONGER be constructed or initialized. Depending upon your application, that can cause things to fail OR not. Maybe you can selectively mark those beans as lazy-init="false"
Yes, we can do that, using context per test case. Prepare a test context xml file with the beans required for your test case.
If you use maven, place the test-context.xml under src/test/resources folder.
Annotate your required test class with the following annotation
#ContextConfiguration(locations = "classpath:test-application-context.xml")
This helps in loading only specific beans for the test case.
If you have two kinds of test cases, then
#Runwith(SpringJUnit4Runner.class)
#ContextConfiguration(locations = "classpath:test-context-case1.xml")
public class TestClassCase1 {}
#Runwith(SpringJUnit4Runner.class)
#ContextConfiguration(locations = "classpath:test-context-case2.xml")
public class TestClassCase2 {}
It's not direct answer, so I'd would not mark as solution. But hope it's helpful.
Generally I see three options.
As VinayVeluri answered nicely. Create separate contexts and launch them in every tests separately.
Create context one time per all tests. Just like here: Reuse spring application context across junit test classes It's a big optimization for testing all tests at once.
Mix those two first points. Create one smaller context only for testing purpose. Mock that, what's never is tested but can throw NPE etc. Like here: Injecting Mockito mocks into a Spring bean to boost up context build. And re-use it like in point 2. One time build for all tests. Personally I'd go with that one.
This one waiting for answer about some kind of smart test runner, which creates minimum needed context per test.
I'm working on an application which has both component and integration tests. The differences between then is: a component test test more than one class (i.e., its inner objects aren't all mocked out - but some of them might be [such as JMS publishers]) and an Integration test is a test that nothing at all is mocked out. In another words, Spring gives you the object and you test it as it is.
So far, so good.
The problem is: in order to be able to replace one dependency or another from the Spring context, I used Springockito (https://bitbucket.org/kubek2k/springockito/wiki/Home) which offers you a way to mock out some bean from the Spring context.
So - in the component tests - I have this:
#RunWith(SpringJUnit4ClassRunner.class)
#DirtiesContext(classMode = AFTER_CLASS)
#ContextConfiguration(loader = SpringockitoContextLoader.class, locations = "classpath:spring-classify-test.xml")
public class....
#Autowired
#ReplaceWithMock
private SomeServiceInterface someServiceInterface;
#Autowired
private Bean bean;
Bean has SomeServiceInterface as a depedency.
public class Bean {
private SomeServiceInterface...
In the case above, SomeServiceInterface will be replaced by a mock. Of course, that example is an oversimplification of the problem - I replace bean with mock objects that are dependecies further down in the object graph.
It's worthy noticing that I load the context from this file: spring-classify-test.xml Also it's wothy noticing that I mark the context as dirty after the execution of the class - so, AFAIK, the next test class must reload the context.
Now the integration test:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(loader = SpringockitoContextLoader.class, locations = {"classpath:/spring-service-integration-test.xml" })
public class ...
#Autowired
private Bean bean;
I load the context from spring-service-integration-test.xml - but SomeServiceInterface inside of Bean is still mocked! The context used in the integration test was changed as well!
If I mark the Integration test with #DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD), the first test in the class will fail because SomeServiceInterface is mocked - but the next test will pass because the context has been refreshed already.
Funny thing is:
If I ask Spring to inject SomeServiceInterface in the Integration Test, it will inject a SomeServiceInterface concrete implementation - not a mock!
I have tried many things to sort out that issue:
Programatically override the beans in the context after the component tests are done using the registerBeanDefinition method from the context
Create a TestExecution listener so I could try to manually refresh the context before the execution of an IntegrationTest
Use the same loader for the different contexts....
This story goes on and on.
Does anyone have any idea?
P.S.: I quite understand that adopting Springockito was a dubious idea - but that decision was not made by me and now we have over 500 tests in the project - hence refactoring them all to remove Springockito will be a lenghty task, therefore it is not a viable option ATM.
The #DirtiesContext annotation is handled by the DirtiesContextTestExecutionListener when it's registered with the TestContextManager. With plain vanilla Spring tests, that listener is registered by default. Perhaps Springockito or something else in your test "component test" is doing something that interferes with the default listener registration?
I suggest to try latest springockito-annotations 1.0.8 with
#DirtiesMocks(classMode = DirtiesMocks.ClassMode.AFTER_CLASS)
See https://bitbucket.org/kubek2k/springockito/wiki/springockito-annotations#!reseting-mocksspies-in-experimental-package-hope-youll-like-it.
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.