I am doing a migration from Spring 4.x to 5.x and am following the recommendation to wrap the object with an ObjectProvider to handle beans that return null: https://stackoverflow.com/a/49393682/10863988
This is the class set up I have:
class ConfigurationClass{
#Autowired
private ObjectProvider<MyObject> myObject;
public SomeOtherClass getSomeOtherClass() {
return new SomeOtherClass(myObject.getIfAvailable());
}
}
class TestSomeOtherClass {
#Mock
MyObject myObject;
#InjectMocks
ConfigurationClass;
SomeOtherClass someOtherClass;
public void setup() {
this.someOtherClass = spy(configuration.getSomeOtherClass());
}
}
The problem is when I run this test. the myObject in the ConfigurationClass returns a null pointer exception.
I've tried adding this to the TestSomeOtherClass but I still can't seem to mock the ObjectProvider<MyObject>:
class TestSomeOtherClass {
#Mock
MyObject myObject;
#Mock
ObjectProvider<MyObject> myObjectObjectProvider;
#InjectMocks
ConfigurationClass;
SomeOtherClass someOtherClass;
public void setup() {
doReturn(myObject).when(myObjectObjectProvider).getIfAvailable();
this.someOtherClass = spy(configuration.getSomeOtherClass());
}
}
Any advice on how to handle this?
You do not tell Mockito to handle it's annotations (#Mock, #InjectMocks) anywhere in your code, so they do not have any effect. By default all non-primitive fields in Java are initialized as null - that's where the NullPointerException comes from.
openMocks/initMocks method
Depending on the version of Mockito you're using, you need to call initMocks() or openMocks() static method from the MockitoAnnotations class:
AutoCloseable openMocks;
#BeforeEach
public void setup() {
// the line below is where the magic happens
openMocks = MockitoAnnotations.openMocks(this);
doReturn(myObject).when(myObjectObjectProvider)
.getIfAvailable();
someOtherClass = spy(configuration.getSomeOtherClass());
}
#AfterEach
void tearDown() throws Exception {
openMocks.close();
}
#Test
void test() {
assertNotNull(someOtherClass);
}
#ExtendWith(MockitoExtension.class)
You can also use the #ExtendWith(MockitoExtension.class) annotation over your class and it has the same effect as the methods described above.
You can find both approaches tested in a GitHub repository I've created (all tests pass).
Related
I have a dummy project where I try figure out how to test pointcuts being triggered.
My project consists of 1 aspect bean which just prints after a foo method is called
#Component
#Aspect
public class SystemArchitecture {
#After("execution(* foo(..))")
public void after() {
System.out.println("#After");
}
}
And a FooServiceImpl with implemented foo method
#Service
public class FooServiceImpl implements FooService{
#Override
public FooDto foo(String msg) {
return new FooDto(msg);
}
}
The code works and and I can see "#After" being printed to console, but I can't check programatically if after pointcut was called using the test below.
#SpringBootTest
public class AspectTest {
#Autowired
private FooService fooService;
#Test
void shouldPass() {
fooService.foo("hello");
}
}
I've also tried using non-bean proxy as was adviced in https://stackoverflow.com/a/56312984/18224588, but this time I'm getting an obvious error cannot extend concrete aspect because my spy proxy is no longer viewed as an aspect:
public class AspectNoContextTest {
#Test
void shouldPass() {
FooService fooService = Mockito.mock(FooService.class);
SystemArchitecture systemArchitecture = Mockito.spy(new SystemArchitecture());
AspectJProxyFactory aspectJProxyFactory = new AspectJProxyFactory(fooService);
aspectJProxyFactory.addAspect(systemArchitecture);
DefaultAopProxyFactory proxyFactory = new DefaultAopProxyFactory();
AopProxy aopProxy = proxyFactory.createAopProxy(aspectJProxyFactory);
FooService proxy = (FooService) aopProxy.getProxy();
proxy.foo("foo");
verify(systemArchitecture, times(1)).after();
}
}
Ok, after some digging, I found that it's possible to accomplish this by making an aspect a #SpyBean. Also AopUtils can be used for performing additional checks
#SpringBootTest
public class AspectTest {
#Autowired
private FooService fooService;
#SpyBean
private SystemArchitecture systemArchitecture;
#Test
void shouldPass() {
assertTrue(AopUtils.isAopProxy(fooService));
assertTrue(AopUtils.isCglibProxy(fooService));
fooService.foo("foo");
verify(systemArchitecture, times(1)).after();
}
}
I have this service:
#Service
public class MyService {
#Value("${my.value}")
private String myValue;
public String someMethodWhichUsesMyValueField() {
return myValue;
}
// Also contains other methods that use some external services accessed with a `RestTemplate`
}
And this integration test:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = MyApplication.class)
public class MyControllerTest {
private MockMvc myControllerMockMvc;
#Autowired
private MyController myController; // This controller injects an instance of MyService
#MockBean
private MyService myServiceMock;
#Before
public void setup() {
this.myControllerMockMvc = MockMvcBuilders.standaloneSetup(myController).build();
when(myServiceMock.someMethodWhichUsesMyValueField()).thenCallRealMethod();
// Also mock the other methods in myServiceMock that call the external webservices
}
#Test
public void someTest() throws Exception {
// use the myControllerMockMvc to call a POST method that calls myService.someMethodWhichUsesMyValueField()
}
}
The problem is that when myService.someMethodWhichUsesMyValueField() is called from the MyController method called from the someTest() method, the field myValue (and all the field annotated with #Autowired) is null even if my application.properties correctly defines my.value=some value.
Is there a way to correctly make myValue inject the value described in the application.properties like any normal #Autowired component?
Use a #SpyBean instead of a #MockBean because then you will have the real Spring object injected with some method that you can mock (thanks for #thomas-andolf's answer for the #Spy hint):
// [...] Nothing to change in annotations
public class MyControllerTest {
// [...] Nothing to change in the other fields
#SpyBean
private MyService myServiceMock;
#Before
public void setup() throws NoSuchFieldException, IllegalAccessException {
// [...] Nothing to change before mocking
// Then, do not mock anymore with .thenCallRealMethod() which is the default behavior with the spy
// And when mocking the other methods in myServiceMock that call the external webservices, use this kind of declaration:
doAnswer(invocation -> /* reply something */).when(myServiceMock).someMethodWhichUsesExternalWebService(any());
// instead of when(myServiceMock.someMethodWhichUsesExternalWebService(any())).thenAnswer(invocation -> /* reply something */);
}
// [...] Nothing to change in the test methods
}
The other (ugly?) way is to inject the field manually directly from the test class, like this for example:
// [...] Nothing to change in annotations
public class MyControllerTest {
// [...] Nothing to change in the other fields
#Value("${my.value}")
private String myValue;
#Before
public void setup() throws NoSuchFieldException, IllegalAccessException {
ReflectionTestUtils.setField(myServiceMock, "myValue", myValue); // thanks to #thomas-andolf's [answer](https://stackoverflow.com/a/55148170/535203)
// // Equivalent with traditional reflection tools:
// Field myValueField = MyService.class.getDeclaredField("myValue");
// myValueField.setAccessible(true);
// myValueField.set(myServiceMock, myValue);
// [...] the rest of the original method
}
// [...] Nothing to change in the test methods
}
You can set your properties in a test class
#TestPropertySource(properties = {"my.value=value"})
public class MyControllerTest {
//
}
A couple of ways you can solve this is the following, you can either use:
Constructor injection:
#Service
public class MyService {
private final myValue;
public MyService(#Value("${my.value}") myValue) {
this.myValue = myValue;
}
public String someMethodWhichUsesMyValueField() {
return myValue;
}
}
And instead of using a mock, just use the class and feed it it's value.
Or you can use:
ReflectionTestUtils.setField(myService, "myValue", "FooBar");
to set the myValue property to foobar.
Or you could change it to use a #Spy instead, which is a "partial mock". A spy is the original class with its original methods and then you can choose to mock some of the methods but keep the logic from the original class in some of the methods.
when I run the test case I get AnObj mocked. this is used from inside the target classes method. when that method gets invoked the 'anOtherObj' is accessed and that is found to be null. Can some one please point out how to make sure 'anOtherObj' is not null so that I dont get nullpointer there?
#Test
public class TestTargetTest {
#Mock
private AnObj anObj;
#InjectMocks
private TestTarget testTarget;
#BeforeMethod
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
#Test
public void testTarget() {
when(anObj.someMethod()).thenCallRealMethod();
testTarget.testTarget();
}
}
#Component
public class TestTarget {
#Autowired
private AnObj anObj;
public void testTarget(){
anObj.someMethod();
}
}
#Component
public class AnObj {
#Autowired
private AnOtherObj anOtherObj;
public void someMethod(){
syso(anOtherObj.toString());
}
}
You need to initialize annotated mocks in your test class.
#BeforeMethod
public void beforeClass() {
MockitoAnnotations.initMocks(this);
}
Why would you care what's inside mock (AnObj)? I assume you haven't yet declared interactions on that mock using Mockito.when.
As mentioned by #Valya that point was valid. I shouldn't have mocked that. I needed to autowire 'AnObj'. Thanks a lot for all the help. It made the difference.
consider my scenario
public class SomeClass {
#Autowired #Qualifier("converter1") private IConverter converter1;
#Autowired #Qualifier("converter2") private IConverter converter2;
public void doSomeAction(String mimeType) {
converter1.execute();
converter2.execute();
}
}
This is my code.
In order to test this
#RunWith(MockitoJUnitRunner.class)
public class SomeClassTest {
#Mock(name="converter1") IConverter converter1;
#Mock(name="converter2") IConverter converter2;
#InjectMocks SomeClass class = new SomeClass();
#Test
public void testGetListOfExcelConverters() throws Exception {
class.doSomeAction("abcd");
}
}
Here the mocks are not getting injected, please help with the proper mechanism for mocking a qualified beans.
If this is not the right way to code using spring, please let me know the correct method for using this.
Not sure what error you are getting, but your test class doesn't compile because you have what looks like you intend to be a variable name using the keyword class. This worked for me:
#RunWith(MockitoJUnitRunner.class)
public class SomeClassTest {
#Mock(name="converter1") IConverter converter1;
#Mock(name="converter2") IConverter converter2;
#InjectMocks
SomeClass clazz = new SomeClass();
#Test
public void testGetListOfExcelConverters() throws Exception {
clazz.doSomeAction("abcd");
verify(converter1).execute();
verify(converter2).execute();
}
}
And by "worked for me" I mean that the test actually ran and passed. Note I added a couple of verify statements to assert that the injected mocks got called.
I used the SomeClass code you provided as-is.
For me, both existing answers were insufficient.
#riddy 's answer did not take into account different test cases.
#jhericks ' answer did not use the Spring context, which caused other issues down the line.
Here's my solution:
#MockBean
#Qualifier("myNamedBean")
private SomeBean someBean;
As simple as that.
You can mock beans using a test configuration:
#Configuration
public class TestConfig {
#Bean
public MyService myService() {
return Mockito.mock( MyService.class );
}
}
I've found this solution:
#RunWith(MockitoJUnitRunner.class)
public class SomeClassTest {
#Mock()
#Qualifier("converter1")
IConverter converter1;
#Mock()
#Qualifier("converter1")
IConverter converter2;
#InjectMocks SomeClassTest testObj = new SomeClassTest();
#Test
public void testGetListOfExcelConverters() throws Exception {
testObj.doSomeAction("abcd");
verify(converter1).execute();
verify(converter2).execute();
}
}
BTW, I haven't found this in doc.
In my app, the #Autowired beans are passed as constructor args. None of the variations (albeit JUnit 5 version) were working. Instead, I had to "kick it old school" and simply instantiate the mocks directly.
public class SomeClass {
private final IConverter converter1;
private final IConverter converter2;
public SomemClass( #Autowired #Qualifier("converter1") conv1,
#Autowired #Qualifier("converter2") conv2 ) {
this.converter1 = conv1;
this.converter2 = conv2;
}
public void doSomeAction(String mimeType) {
converter1.execute();
converter2.execute();
}
}
public class SomeClassTest {
IConverter converter1;
IConverter converter2;
SomeClass pojo;
#BeforeEach
public void setup() {
converter1 = Mockito.mock( IConverter.class );
converter2 = Mockito.mock( IConverter.class );
pojo = new SomeClass( converter1, converter2 );
}
#Test
public void testGetListOfExcelConverters() throws Exception {
pojo.doSomeAction("abcd");
}
}
I have a custom ConstraintValidator:
#Component
public class FooValidator implements ConstraintValidator<FooAnnotation, String> {
#Inject
private FooRepository fooRepository;
#Override
public void initialize(FooAnnotation foo) {
}
#Override
public boolean isValid(String code, ConstraintValidatorContext context) {
Foo foo = fooRepository.findByCode(code);
//My code
}
}
In my Junit tests and MockMVC I call the url but fooRepository bean validator is always null.
How I can inject it in my test controller? I tried to create a mock repository but it is also null.
My source code test:
public class FooControllerTest {
#InjectMocks
private FooController fooController;
private MockMvc mockMvc;
#Before
public void setup() {
// Process mock annotations
MockitoAnnotations.initMocks(this);
// Setup Spring test in standalone mode
this.mockMvc = MockMvcBuilders.standaloneSetup(fooController)
.setCustomArgumentResolvers(new PageableHandlerMethodArgumentResolver())
.build();
}
#Test
public void testSave() throws Exception {
Foo foo = new Foo();
// given
//My code...
// when
// then
// with errors
this.mockMvc.perform(post("/foo/update")
.param("name", "asdfasd")
.sessionAttr("foo", foo))
.andExpect(model().hasErrors())
.andExpect(model().attributeHasFieldErrors("foo", "name"));
}
}
You should add a #ContextConfiguration to your test, among other things.