Mockito: Spying function calls inside Functional interfaces? - spring-boot

Mockito doesn't seem to be able to spy on function calls inside Functional interfaces. Suppose I have a simple Spring Boot App with a service:
#Service
public class TestService {
Function<Integer, Integer> mapping = this::add2;
Integer add2(Integer integer) {
return integer + 2;
}
}
And a test:
#SpyBean
TestService testService;
#Test
public void mockitoTest2(){
doReturn(6).when(testService).add2(2);
System.out.println(testService.mapping.apply(2));
}
The test will return 4 instead of 6. Is this expected, or worth a bug report?

This is expected. Mockito creates a spy by making a shallow copy, and the method reference this::add2 gets copied over while keeping the reference to the old this.
TestService myTestService = new TestService();
TestService mySpy = Mockito.spy(myTestService);
In this example, mySpy is an instance of a generated subclass of TestService, which has all of its overridable methods overridden to delegate to Mockito, and all of its instance state shallow-copied from myTestService. This means that myTestService.mapping == mySpy.mapping, which also implies that the reference to this (meaning myTestService) is captured in the Function is copied over.
Method references applied to an instance capture that instance, as on the Oracle page on Method References under "Kinds of Method References". The object that receives the add2 call is the original, not the spy, so you get the original behavior (4) and not the spy-influenced behavior (6).
This should be somewhat intuitive: You can call the Function<Integer, Integer> without passing around a TestService instance, so it's pretty reasonable that the Function contains an implicit reference to a TestService implementation. You're seeing this behavior because the spy instance has its state copied from a real instance after the Function is initialized and this is stored.
Consider this alternative, which you could define on TestService:
BiFunction<TestService, Integer, Integer> mapping2 = TestService::add2;
Here, the function mapping2 doesn't apply to a particular object, but instead applies to any instance of TestService passed in. Consequently, your test would call this:
#Test
public void mockitoTest2(){
doReturn(6).when(testService).add2(2);
System.out.println(testService.mapping2.apply(testService, 2));
}
...and because you are passing in your spy testService, it will handle the virtual method call to add2 and invoke the behavior set on the spy (returning 6). There is no implicitly-saved this, so your function works as you'd expect.
See also: Mockito runnable: wanted but not invoked?

Related

Does Transactional.TxType.REQUIRES_NEW start a new transaction when called from the same bean?

AFAIK
In proxy mode (which is the default), only 'external' method calls
coming in through the proxy will be intercepted. This means that
'self-invocation', i.e. a method within the target object calling some
other method of the target object, won't lead to an actual transaction
at runtime even if the invoked method is marked with #Transactional!
But here is: Transactional.TxType.REQUIRES_NEW
Will be the second transaction created?
#Service
public class SomeService {
#Transactional
public void doSomeLogic() {
// some logic
doOtherThings();
// some logic
}
#Transactional(Transactional.TxType.REQUIRES_NEW)
private void doOtherThings() {
// some logic
}
To get an answer to this question, you need to know how a proxy works.
When you annotate a method inside a bean, the proxy will wrap that bean with the appropriate logic. This means that if you call that annotated method of your bean, the request will first be sent to the proxy object (named with $), which will then call the bean's method. If this method calls another method of the same bean, it will call it without invoking a proxy which has a logic, e.x., of transaction management.
Example: Here is the code which will be wrapped by proxy and an appropriate diagram of its work.
#Service
public class SomeService {
#Transactional
public void foo() {
// this next method invocation is a direct call on the 'this' reference
bar();
}
#Transactional(Transactional.TxType.REQUIRES_NEW)
public void bar() {
// some logic...
}
}
Source
Hence, the answer is No.
Hope that's a little bit more clear now.

#Async is not working if I give it at service level in spring-boot

I am autowiring service in controller. And in service, I have a scenario where I need to throw an exception and DB changes also. So, I tried #Async
#Transactional
public void verifyOtp(OtpDto otpDto)
...
if(xyz){
deactivateOtp(emp.getId());
throw new ServException("Mobile no requested is already assigned", "error-code");
}
}
#Async
#Transactional //with or without
public void deactivateOtp(Integer id){
otpRepo.deactivateOtp(id);
}
public interface OtpRepository extends JpaRepository<Otp, Integer> {
#Modifying
#Query("UPDATE Otp SET isActive = 0 WHERE id = :id")
public void deactiveOtp(#Param("id") Integer id);
This is not creating new thread. But, if I gives at repo, it works
public void deactivateOtp(Integer id){
otpRepo.deactivateOtp(id);
}
public interface OtpRepository extends JpaRepository<Otp, Integer> {
#Async
#Transactional
#Modifying
#Query("UPDATE Otp SET isActive = 0 WHERE id = :id")
public void deactiveOtp(#Param("id") Integer id);
First of all check that the service is wrapped into proxy (you can place a breakpoint in controller and see the reference to the service, it will be with proxy). Otherwise there is something wrong with the configuration and #Transactional/#Async won't work till you fix that.
Now, assuming this is not an issue, there is an issue in the code:
When the controller calls service.verifyOtp it goes to the proxy (to handle the transaction) and then to your implementation.
But when it reaches your implementation and you call the method that belongs to the same impl, it doesn't pass through the proxy again, instead it directly goes to the deactivateOtp as if there is no spring at all here. Of course, #Async doesn't work.
In terms of resolution:
Consider using self injection if you work with spring 4.3+. Read this thread for more information.
Alternatively, refactor your code so that the deactivateOtp will be a public method of another class. In this case the call won't be "internal" anymore, it will path through Proxy hence the #Async will work.
This is discussed many times.
Basically, Spring AOP will not intercept local method call
(in your case call deactivateOtp() within the same class)
You can read more about this behavior here: Understanding AOP proxies
Highlight:
The key thing to understand here is that the client code inside the main(..) of the Main class has a reference to the proxy. This means that method calls on that object reference will be calls on the proxy, and as such the proxy will be able to delegate to all of the interceptors (advice) that are relevant to that particular method call. However, once the call has finally reached the target object, the SimplePojo reference in this case, any method calls that it may make on itself, such as this.bar() or this.foo(), are going to be invoked against the this reference, and not the proxy. This has important implications. It means that self-invocation is not going to result in the advice associated with a method invocation getting a chance to execute.

How to mock private method in public method in Spring Boot with JUnit

I'd like you ask a few questions and ask you for advice:
I want to test my public method (I use Spring Boot, Mockito, JUnit):
#Service
public class MyClass{
public Long getClientId(List<String> nameSurname) throws AuthorizationException {
Long operatorId;
if(...){
(... something not interesting ...)
User user = getUserByLogin("AnthonyGates2");
operatorId = nonNull(user) ? user.getOperatorId() : null;
} else {
List<User> users = getUserListByLogin("AnthinyGates");
operatorId = isNotEmpty(users) ? return operatorId;
return operatorId;
}
How to test the method getClientId?
Methods getUserByLogin and getUserListByLogin are private in this class (MyClass) but I have to mock the results of these private methods because these methods retrieve data from an external service.
These private methods looks like:
User user = DelegateImpl.getDelegate().getUserByLogin(nameAndSurname);
DelegateImpl.getDelegate().getUserByLogin get data from database and that data have to be mocked like:
when(DelegateImpl.getDelegate().getUserByLogin(any())).thenReturn(user);
How can I test my public class? Should I use PowerMock/PowerMockito? Making these methods public is in my opinion ugly because these methods are called only in MyClass. I can't find a good tutorial in Internet for my case (Spring Boot, Mockito, JUnit).
Thank you very much for all your tips!
Best regards
Matthew
Test the unit only by calling the public methods. I think that your example is a class in the service layer (contains business logic) and the two getUser... methods should be in a different class (I think in the data layer) where they can be public. Inject that class via the constructor as a dependency (in the service object) so you can mock it when testing the service class. The data layer class (with the getUser... methods) can also be tested by it's own unit tests.
If you are not able to unit test a method/class then it most probably means that it just does too much. Try extracting your private methods to a separate class. It does not need to be public - you can e.g. have it package-local in the same package.
Later, in the test, you would have to inject a mock of this class and simulate its behaviour.
The setup of MyClass in its unit test could look similar to this:
AnotherClass anotherClassMock = Mockito.mock(AnotherClass.class);
MyClass myClass = new MyClass(anotherClassMock);
Where AnotherClass would have methods getUserListByLogin and getUserByLogin.
EDIT:
It seems that the logic within in your private methods already call an external class. The problem is that you obtain an instance of an object via a call to a static getDelegate() method in another class.
Here's what you can do:
Create a new field in MyClass which would have the same type as the one returned by getDelegate() method (I don't know what that is, I'll call it Delegate)
Have 2 constructors: a default one which would assign the result of getDelegate method to your new field and another one which would take an instance of Delegate as a parameter and assign it to your field
In tests use the second constructor to create an instance of MyClass and pass a mock of Delegate class
It would look more ore less like this:
class MyClass() {
private Delegate delegate;
MyClass() {
this.delegate = DelegateImpl.getDelegate();
}
MyClass(Delegate delegate) {
this.delegate = delegate;
}
// ... the rest
}

How to unit test a service which sets attribute to newly created object

I want to write unit test for one of my service to verify certain fields get assigned.
public void createNewRecord(Dto dto) {
Record record = new Record();
record.setName(dto.getName());
record.setDetail(dto.getDetail());
repo.save(record);
}
I don't have a constructor for dto because a record has many attributes and some of them get assigned from other methods. My previous plan is: mock the record and verify setName() and setDetail methods are called once. But there is no way to inject mocked record into this service. Do I have to change my previous code? Any thought is appreciated.
There are several approaches:
First:
change method to this
public void createNewRecord(Record record, Dao dao)
Second:
Use PowerMockito to mock constructor
Third:
Use factory or com.google.inject.Provider for construct record (I prefer this)
Forth:
If record constructor is simple and setters for record also don't have some special logic then you can mock only repo and verify repo's argument.
Mocks should be used for mocking a dependency of the tested object, not for mocking an inner object in the tested method.
Why don't you mock the repo instance and then you would verify with you mock api that repo.save() is called with the expected record ?
It seems to be a straight way to unit test your method.
You can use Mockito and its #Captor annotation to capture the arguments passed to the repo instance. You can #Mock the Dto object to create expectations for the getName() and getDetail() methods and assert that the results of invoking the setName() and setDetail() methods on the captured Record instance are the same values expected from the Dto object. For example:
#Mock
private Repo repo;
#Mock
private Dto dto;
#Captor
private ArgumentCaptor<Record> recordArgumentCaptor;
private Service service;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
this.service = new Service(repo);
}
#Test
public void shouldCreateNewRecord() {
when(dto.getName()).thenReturn("NAME");
when(dto.getDetail()).thenReturn("DETAIL");
service.createNewRecord(dto);
verify(repo).save(recordArgumentCaptor.capture());
Record record = recordArgumentCaptor.getValue();
assertThat(record.getName(), is(equalTo(dto.getName())));
assertThat(record.getDetail(), is(equalTo(dto.getDetail())));
}

Why doesn't Mockito's when() get triggered?

I need to test a service class, but when I try to mock the dao class, it doesn't get triggered, thus not able to use ThenReturn().
I think that the problem is because I use an interface for my Dao and #Autowired in the service class (Spring MVC 3.1):
The interface:
public interface TestDao {
int createObject(Test test) throws NamingException;
}
The implementation:
#Repository
public class TestDaoImpl implements TestDao {
#Override
public int createObject(Test test) {
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(new InsertNewTest(test), keyHolder);
return ((java.math.BigDecimal)keyHolder.getKey()).intValue();
}
}
The service:
public class RegTest {
#Autowired
TestDao testDao;
public int regTest(int .....) {
.
.
int cabotageId = testDao.createObject(test);
}
}
In the test I have:
#RunWith(MockitoJUnitRunner.class)
public class TestRegService {
#InjectMocks
private RegTest regTest = new RegTest();
#Mock
TestDao testDao;
#Test()
public void test() {
.
when(testDao.createObject(null)).thenReturn(100);
.
}
testDao.createObject(null) returns 0 (due to being mock'ed) and not 100 as I is trying to achieve.
Can anybody help, please?
Problem solved!
It was the passing test-object to createObject() that did not match. Using
testDao.createObject(any(Test.class))
did the trick!
If your test is actually passing a value to createObject, then when(testDao.createObject(null)... never gets matched. Rather than matching on null, you could match any instance of Test with testDao.createObject(any(Test.class))...
Also when you tried later to supply new Test() as the argument to match, it will literally try to match on that exact instance of Test, but presumably your real code is new-ing up a different one. So the use of Matchers.any(Test.class) as the parameter to match is the way to go.
Mockito injection mechanism don't know about Spring #Autowired or CDI #Inject annotations. It just tries to find the best candidate given the type and the name of the mock, and it can lookup private fields too. See the javadoc of #InjectMocks : http://docs.mockito.googlecode.com/hg/1.9.0/org/mockito/InjectMocks.html
The semantic you are using is correct, though if you are experiencing issues, I would rather look for incorrect interactions or incorrect arguments.
Are you sure the test variable in regTest.regTest(int...) is really null when passed to testDao.createObject(test) ?
I don't know if this is a typo in the example, but you have RegTest.regTest() calling createTest() rather than createObject(). Otherwise, I don't think #Autowired has anything to do with it, since your test itself is not running in a container with Spring management. If it is not a typo, and createTest is in fact a real and different method from createObject, then the default behaviour of a mocked object in Mockito is to return the appropriately-typed zero for numeric return types.
I think that you're right about the autowire not getting called. You could inject the dao yourself using the setTestDao() call instead. Mockito also supports spy which allows you to trace the objects code and just replace functions instead.

Resources