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;
}
Related
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
I want to use #Qualifier to dynamically specifying parameters? how to do it ?
#Qualifier("two") 'two' as a parameter ,can be 'one' 'three' or other.
Can i use aop dynamically design 'two'?
means I want to change the name of service with a #Qualifier by parameters.
the parameter from the url 'Token'.
case: url: http://localhost:8080/insert/order, token has a parameter: companyId = one
#RestController
public class ApiWebService {
#Autowired
#Qualifier("two")
//#Qualifier("one")
private BaseService baseService;
#GetMapping("insert/order")
public void test() {
baseService.insertOrder();
}
}
#Service("one")
public class CompanyOneService extends BaseService {
#Override
public void insertOrder() {
System.out.println("conpanyOne");
System.out.println("baseInsertOrder");
}
}
#Service("two")
public class CompanyTwoService extends BaseService {
#Override
public void insertOrder(){
System.out.println("companyTwo");
System.out.println("baseInsertOrder");
}
}
three
four
...
#Service
public class BaseService {
public void insertOrder(){
System.out.println("baseInsertOrder");
}
}
你好 !
No you cannot , mostly because the attribute in Java annotation does not allow to assign with variables.
Actually you want to choose an implementation to use based on some runtime conditions(i.e.companyId in your case). You can achieve it using factory pattern with #Configuration and #Bean which is much more elegant and easier to understand than your ugly AOP solution:
First define a factory:
#Configuration
public class ServiceFactory{
#Bean
public BaseService companyOneService(){
return new CompanyOneService();
}
#Bean
public BaseService companyTwoService(){
return new CompanyTwoService();
}
public BaseService getService(Integer companyId){
if(companyId == 1){
return companyOneService();
}else if(company==2){
return companyTwoService();
}else{
//blablablab
}
}
}
In the controller , inject the ServiceFactory to get the related Service based on the the company Id
#RestController
public class ApiWebService {
#Autowired
private ServiceFactory serviceFactory;
#GetMapping("insert/order")
public void test() {
Integer companyId = getCompanyIdFromToken(httpServletRequest);
BaseService service = serviceFactory.getService(companyId);
service.blablabla();
}
}
Inject (autowire) ApplicationContext into your class and use one of getBeans* method to find the exact bean you need.
aspect
#Aspect
#Component
public class ApiAspect {
#Pointcut("execution(* com.example.demo.control.ApiWebService.*(..))")
public void apiInputWebService() {
}
#Before("apiInputWebService()")
public void apiInputAuth(JoinPoint joinPoint) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
.getRequestAttributes())
.getRequest();
String token = request.getHeader("Authorization");
//compangId can be from token
String compangId = "one";
Object target = joinPoint.getTarget();
Method method = target.getClass().getMethod("before", String.class);
method.invoke(target, compangId);
}
}
control
#RestController
public class ApiWebService {
private ApiService baseService;
#Autowired
private ApplicationContext applicationContext;
public void before(String company) {
baseService = (ApiService) applicationContext.getBean(company);
}
#GetMapping("insert/order")
public void test() {
baseService.insertOrder();
}
}
service
#Service
public class ApiService {
public void insertOrder(){
System.out.println("baseInsertOrder");
}
}
#Service("one")
public class CompanyOneService extends ApiService {
#Override
public void insertOrder() {
System.out.println("conpanyOne");
System.out.println("baseInsertOrder");
}
}
#Service("two")
public class CompanyTwoService extends ApiService {
#Override
public void insertOrder(){
System.out.println("companyTwo");
System.out.println("baseInsertOrder");
}
}
I have encountered with the following issue. I would like to create Spring #Component with generic
#Component
public class ResponseDtoValidator<DTO> {
public ResponseEntity<DTO> methodToInvoke(DTO dto) {
return Optional.ofNullable(dto).map(result -> new >ResponseEntity<>
(result, HttpStatus.OK))
.orElse(new ResponseEntity<>(HttpStatus.NOT_FOUND)); }
}
#Controller
public class SomeController {
#Inject
private ResponseDtoValidator<DTO1> responseDtoValidator1;
#Inject
private ResponseDtoValidator<DTO2> responseDtoValidator2;
public void someMethod() {
DTO1 dto1 = new DTO1();
DTO2 dto2 = new DTO2();
responseDtoValidator1.methodToInvoke(dto1);
responseDtoValidator2.methodToInvoke(dto2);
}
}
Can I inject this Component like above? Actually, I have tried and it seems to work properly, can you please confirm that I am correct or not?
Firstly, a spring bean can not be injected in itself.
In regards to your question,yes it is injectable but dont use generic signs when injecting. Just inject it normally. Generic type is send when a method of generic class is utilizing.
For instance;
#Component
public class ResponseDtoValidator<DTO> {
public void getAbc(List<DTO> aList) {}
}
Then;
public class Test {
#Autowired // or #Inject
private ResponseDtoValidator responseDtoValidator;
public void testMethod() {
responseDtoValidator.getAbc(List<EnterATypeInHere> aList);
}
}
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);
...
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