In the following case, can I test "MainServiceImpl"?
【What I want to do】
・Test target object is "MainServiceImpl".
・SubMainServiceImpl is used without mock.
・SubSubMainServiceImpl.subSubSayHello method is used with mock.
You may say "double autowired class is not recommended to mock." I know, but I want to know the above test is technically possible.
//Test Target Object
#Service
public class MainServiceImpl implements MainService {
#Autowired
private SubMainService subMainService;
#Override
public String mainSayHello() {
return "MainSayHello. Also..." + subMainService.subSayHello();
}
}
//MainServiceImpl depends on this class.
#Service
public class SubMainServiceImpl implements SubMainService {
#Autowired
private SubSubMainService subSubMainService;
#Override
public String subSayHello() {
return "SubSayHello. Also..." + subSubMainService.subSubSayHello();
}
}
//SubSubServiceImpl depends on this class.
#Service
public class SubSubMainServiceImpl implements SubSubMainService {
#Override
public String subSubSayHello() {
return "SubSubSayHello";
}
}
As a side note, I can mock direct-autowired class.
How can I mock "double" autowired class?
//Direct(not double) autowired class is mocked.
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class MockMainServiceTest {
#InjectMocks
MainServiceImpl mainService;
#Mock
SubMainService subMainService;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
#Test
public void SubMainService_Mocked(){
doReturn("SubMainService_Mocked").when(subMainService).subSayHello();
Assert.assertThat(mainService.mainSayHello() ,is("MainSayHello. Also...SubMainService_Mocked"));
}
}
Technology
SpringBoot 2.5.4
JUnit 4.2
Java 1.8
Related
I am trying to write unit tests for a class having spring retry using the springRunner. But my #Autowired beans are null . Could you please let me know what I did wrong?
Below is my test class
#RunWith(SpringRunner.class)
#ContextConfiguration
public class DeltaHelperTest {
#Autowired
private DeltaHelper deltaHelper;
#Before
public void setUp() { System.setProperty("delta.process.retries", "2"); }
#After
public void validate() { validateMockitoUsage(); }
#Test
public void retriesAfterOneFailAndThenPass() throws Exception {
when(deltaHelper.restService.call(any(), any())).thenThrow(new HttpException());
deltaHelper.process(any(),any());
verify(deltaHelper, times(2)).process(any(), any());
}
#Configuration
#EnableRetry
#EnableAspectJAutoProxy(proxyTargetClass=true)
#Import(MockitoSkipAutowireConfiguration.class)
public static class Application {
#Bean
public DeltaHelper deltaHelper() {
DeltaHelper deltaHelper = new DeltaHelper();
deltaHelper.myStorageService= myStorageService();
deltaHelper.restService = restService();
return deltaHelper;
}
#Bean
public MyStorageService myStorageService() {
return new MyStorageService();
}
#Bean
public MyRestService restService() {
return new MyRestService();
}
#Bean
public MyRepo myRepository() {
return mock(MyRepo.class);
}
}
#Configuration
public static class MockitoSkipAutowireConfiguration {
#Bean MockBeanFactory mockBeanFactory() {
return new MockBeanFactory();
}
private static class MockBeanFactory extends InstantiationAwareBeanPostProcessorAdapter {
#Override
public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
return !mockingDetails(bean).isMock();
}
}
}
}
Here test service is null on deltaHelper object .
MyRepo.class is mocked as it has some more #autowired bean reference
Attaching other classes here
#Component
public class DeltaHelper {
#Autowired
MyRestService restService;
#Autowired
MyStorageService myStorageService;
#NotNull
#Retryable(
value = Exception.class,
maxAttemptsExpression = "${delta.process.retries}"
)
public String process(String api, HttpEntity<?> entity) {
return restService.call(api, entity);
}
#Recover
public String recover(Exception e, String api, HttpEntity<?> entity) {
myStorageService.save(api);
return "recover";
}
}
#Service
public class MyStorageService {
#Autowired
MyRepo myRepo;
#Async
public MyEntity save(String api) {
return myRepo.save(new MyEntity(api, System.currentTimeMillis()));
}
}
public class MyRestService extends org.springframework.web.client.RestTemplate {
}
Thank you
Tried MockitoJUnitRunner, But found that #Retryable works only when running with Spring
I'm not sure why you are trying to test framework functionality such as retry. Generally, you can assume that framework components have been tested thoroughly by the framework authors.
Ignoring that, I can see at least two problems:
deltaHelper is not a mock, but your SUT, yet you try to set up method calls. If you mock your SUT, you are no longer testing your class, you are testing the mock. If you want your call to fail, don't mock the call, but mock its dependencies (e.g. MyRestService restService) and have calls on the dependency throw an exception.
You pass ArgumentMatchers.any() in your real method call (the "act" part), but any() unconditionally returns null (not some magic object). If you want to act on your SUT, you must pass real values. any is for setting up mocks or verifying calls on mocks.
For completeness' sake, here's the source of any():
public static <T> T any() {
reportMatcher(Any.ANY);
return null;
}
I have a controller like this
#Path("/")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public class AccountController implements CRUDController<Long, Account> {
private AccountDao accountDao;
private AccountService accountService;
#Inject
public AccountController(AccountDao accountDao, AccountService accountService) {
this.accountDao = accountDao;
this.accountService = accountService;
}
...
I'm injecting AccountDao and AccountService using
ResourceConfig config = new ResourceConfig()
.packages("controller", "exception")
.register(new MyDIBinder());
Where MyDIBinder is contains all the bindings (e.g
AccountDaoImpl accountDaoImpl = new AccountDaoImpl();
bind(accountDaoImpl).to(AccountDao.class);
)
Now I want to write a unit test for this controller, is it possible to inject the whole AccountController instance with all of it's transitive dependencies into the test?
Something like
#Inject
AccountController accountController;
You can use the main IoC container, and just explicitly inject the test class. Jersey uses HK2 as its DI framework, and its IoC container is the ServiceLocator, which has a method inject(anyObject) that can inject any objects with dependencies that are in its registry.
For example you could do something like
public class InjectionTest {
#Inject
private TestController controller;
#Before
public void setUp() {
final Binder b = new AbstractBinder() {
#Override
public void configure() {
bindAsContract(TestController.class);
}
};
final ServiceLocator locator = ServiceLocatorUtilities.bind(new TestBinder(), b);
locator.inject(this);
}
#Test
public void doTest() {
assertNotNull(controller);
String response = controller.get();
assertEquals("Hello Tests", response);
}
}
The ServiceLocatorUtilities class is a helper class that allows us to easily create the ServiceLocator, and then we just call inject(this) to inject the InjectionTest.
If it seems repetitive to do this for all your controller tests, you may want to create an abstract base test class. Maybe something like
public abstract class AbstractControllerTest {
protected ServiceLocator locator;
private final Class<?> controllerClass;
protected AbstractControllerTest(Class<?> controllerClass) {
this.controllerClass = controllerClass;
}
#Before
public void setUp() {
final AbstractBinder binder = new AbstractBinder() {
#Override
public void configure() {
bindAsContract(controllerClass);
}
};
locator = ServiceLocatorUtilities.bind(new TestBinder(), binder);
locator.inject(this);
}
#After
public void tearDown() {
if (locator != null) {
locator.shutdown();
}
}
}
Then in your concrete class
public class TestControllerTest extends AbstractControllerTest {
public TestControllerTest() {
super(TestController.class);
}
#Inject
private TestController controller;
#Test
public void doTest() {
assertNotNull(controller);
assertEquals("Hello Tests", controller.get());
}
}
If you spent some more time, I'm sure you could come up with a better abstract test class design. It was the first thing that came to mind for me.
Note: For anything request scoped, you mayb need to just mock it. When running the unit tests, there is no request context, so the test will fail.
See Also:
Using Jersey's Dependency Injection in a Standalone application
HK2 documentation
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.hk2.utilities.ServiceLocatorUtilities;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
#BeforeClass
public static void doTest() {
ServiceLocator serviceLocator = ServiceLocatorUtilities.bind(new AbstractBinder() {
#Override
protected void configure() {
bindAsContract(YourClass1.class);
bindAsContract(YourClass2.class);
bindAsContract(YourClass3.class);
}
});
YourClass1 yourClass1 = serviceLocator.getService(YourClass1.class);
...
I have below class scenario. While testing MyTestableClass, I wish to process Autowired class.
I would like to mock only variable in AutoWired class.
sample class is as below-
public class MyTestableClass {
#Autowired
private MyServiceClass service;
public void handleError(){
...
service.doSomething();
}
}
public class MyServiceClass {
#Autowired
private JMSChannel channel;
public void doSomething(){
System.out.println("Inside Service class");
.....
channel.isAvailable();
.....
}
}
#RunWith(MockitoJUnitRunner.class)
public class MyTestableClassTest {
private MyTestableClass testClass= new MyTestableClass();
private JMSChannel channel;
#Before
public void init(){
channel= mock(JMSChannel.class);
when(channel.isAvailable()).thenReturn(Boolean.TRUE);
}
#Test
public void test(){
testClass.handleError();
}
}
For example, Console should give me "Inside Service class" before returning true.
Thanks in Advance !
You need to create and instance of your service (or a mock of it) and set its channel to your mocked one, then set MyTestableClass#service to this one. Something like:
#Before
public void init(){
channel= mock(JMSChannel.class);
when(channel.isAvailable()).thenReturn(Boolean.TRUE);
MyServiceClass service = new MyServiceClass();
ReflectionTestUtils.setField(service, "channel", channel);
myTestableClass = new MyTestableClass();
ReflectionTestUtils.setField(myTestableClass, "service", service);
}
with ReflectionTestUtils from spring-test (NB: you can use a setter instead)
It is said in manual, that
The Test annotation tells JUnit that the public void method to which
it is attached can be run as a test case. To run the method, JUnit
first constructs a fresh instance of the class then invokes the
annotated method. Any exceptions thrown by the test will be reported
by JUnit as a failure. If no exceptions are thrown, the test is
assumed to have succeeded.
which may mean, that for each #Test method the context should be initialized again. This is also confirmed in this answer: https://stackoverflow.com/a/1564309/258483
Simultaneously, I see opposite in my experiment:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = SpringJUnit4ClassRunnerDemo._Config.class)
public class SpringJUnit4ClassRunnerDemo {
public static class Bean1 {
{
System.out.println("Bean1 constructor called");
}
}
public static class Bean2 {
{
System.out.println("Bean2 constructor called");
}
private Bean1 bean1;
public Bean1 getBean1() {
return bean1;
}
#Autowired
public void setBean1(Bean1 bean1) {
this.bean1 = bean1;
System.out.println("Bean2.bean1 property set");
}
}
#Configuration
public static class _Config {
#Bean
public Bean1 bean1() {
return new Bean1();
}
#Bean
public Bean2 bean2() {
return new Bean2();
}
}
#Autowired
private Bean1 bean1;
#Autowired
private Bean2 bean2;
#Test
public void testBean1() {
assertNotNull(bean1);
System.out.println("testBean1() done");
}
#Test
public void testBean2() {
assertNotNull(bean2);
assertSame(bean2.getBean1(), bean1);
System.out.println("testBean2() done");
}
}
This code outputs
Bean1 constructor called
Bean2 constructor called
Bean2.bean1 property set
testBean1() done
testBean2() done
which may mean, that context is not initialized second time before second test.
What is actual and correct behavior and how to control it?
If you want the Spring context reloaded between test methods, you need to use the #DirtiesContext annotation: https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/test/annotation/DirtiesContext.html
I'm using Spring since a few months for now, and I thought dependency injection with the #Autowired annotation also required a setter for the field to inject.
So, I am using it like this:
#Controller
public class MyController {
#Autowired
MyService injectedService;
public void setMyService(MyService injectedService) {
this.injectedService = injectedService;
}
...
}
But I've tried this today:
#Controller
public class MyController {
#Autowired
MyService injectedService;
...
}
And oh surprise, no compilation errors, no errors at startup, the application is running perfectly...
So my question is, is the setter required for dependency injection with the #Autowired annotation?
I'm using Spring 3.1.1.
You don't need a setter with the #Autowired, the value is set by reflection.
Check this post for complete explanation How does Spring #Autowired work
No, if Java security policy allows Spring to change the access rights for the package protected field a setter is not required.
package com.techighost;
public class Test {
private Test2 test2;
public Test() {
System.out.println("Test constructor called");
}
public Test2 getTest2() {
return test2;
}
}
package com.techighost;
public class Test2 {
private int i;
public Test2() {
i=5;
System.out.println("test2 constructor called");
}
public int getI() {
return i;
}
}
package com.techighost;
import java.lang.reflect.Field;
public class TestReflection {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
Class<?> class1 = Class.forName("com.techighost.Test");
Object object = class1.newInstance();
Field[] field = class1.getDeclaredFields();
field[0].setAccessible(true);
System.out.println(field[0].getType());
field[0].set(object,Class.forName(field[0].getType().getName()).newInstance() );
Test2 test2 = ((Test)object).getTest2();
System.out.println("i="+test2.getI());
}
}
This is how it is done using reflection.