Mockito expecting argument mismatch - java

I'm writing a Unit test with JUnit 4 and Mockito 4.6.1. I'm mocking an interface method with a specific input value. When a different value is passed, I'm expecting an argument mismatch error, but it's not being thrown.
Consider the following example:
public class SomeTest {
#Test
public void test() {
SomeInterface mock = Mockito.mock(SomeInterface.class, Mockito.withSettings().strictness(Strictness.STRICT_STUBS));
// Only mock test(true) and not test(false).
Mockito.when(mock.test(true)).thenReturn(1);
Assert.assertEquals(mock.test(true), 1);
Assert.assertEquals(
// Expecting argument mismatch error
mock.test(false),
2
);
}
interface SomeInterface {
int test(Boolean arg);
}
}
I'm mocking SomeInterface.test(true) to return 1. This works as expected. Now when I call mock.test(false), I'm expecting an argument mismatch because it's not defined in the mock setup and strict mode is enabled. Instead, it returns 0 as if it was mocked.
Am I missing something that's causing this to happen?

Problem 1. We must enable STRICT_STUBS for test.
STRICT_STUBS - ensures clean tests, reduces test code duplication,
improves debuggability. Best combination of flexibility and
productivity. Highly recommended. Planned as default for Mockito v4.
Enable it via MockitoRule, MockitoJUnitRunner or MockitoSession. See
STRICT_STUBS for the details.
According to documentation it can be done via MockitoJUnitRunner.StrictStubs.class
#RunWith(MockitoJUnitRunner.StrictStubs.class)
Or strictness rule
#Rule public MockitoRule rule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
Problem 2. Argument strictness does not work when mock definition and mock invocation perform in one source class.
According to Mockito source code:
If stubbing and invocation are in the same source file we assume they are in
the test code,
and we don't flag it as mismatch:
Summarise. The working test will be:
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.quality.Strictness;
import static org.mockito.Mockito.*;
#RunWith(MockitoJUnitRunner.StrictStubs.class)
public class SomeTest {
#Test
public void test() {
SomeInterface mock = Mockito.mock(SomeInterface.class, withSettings().strictness(Strictness.STRICT_STUBS));
Mockito.when(mock.test(true)).thenReturn(1);
SomeInterfaceImpl someInterface = new SomeInterfaceImpl(mock);
Assert.assertEquals(someInterface.test(), 2);
}
}
public interface SomeInterface {
int test(Boolean arg);
}
public class SomeInterfaceImpl {
private SomeInterface someInterface;
public SomeInterfaceImpl(SomeInterface someInterface) {
this.someInterface = someInterface;
}
public int test() {
return someInterface.test(false);
}
}
On execution we get:
org.mockito.exceptions.misusing.PotentialStubbingProblem:
Strict stubbing argument mismatch. Please check:
- this invocation of 'test' method:
someInterface.test(false);
-> at com.example.SomeInterfaceImpl.test(SomeInterfaceImpl.java:10)
- has following stubbing(s) with different arguments:
1. someInterface.test(true);
-> at com.example.SomeTest.test(SomeTest.java:26)
Typically, stubbing argument mismatch indicates user mistake when writing tests.
Mockito fails early so that you can debug potential problem easily.
However, there are legit scenarios when this exception generates false negative signal:
- stubbing the same method multiple times using 'given().will()' or 'when().then()' API
Please use 'will().given()' or 'doReturn().when()' API for stubbing.
- stubbed method is intentionally invoked with different arguments by code under test
Please use default or 'silent' JUnit Rule (equivalent of Strictness.LENIENT).
For more information see javadoc for PotentialStubbingProblem class.
More preferable solution
You can define the default answer with IllegalArgumentException for your mock in case if mock is executed with mismatch arguments. This solution will work always without source file restrictions.
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import static org.mockito.Mockito.doReturn;
#RunWith(MockitoJUnitRunner.class)
public class SomeTest {
#Test
public void test() {
SomeInterface mock = Mockito.mock(SomeInterface.class, Mockito.withSettings().defaultAnswer(
invocation -> {
throw new IllegalArgumentException(String.format("You cannot invoke %s with %s", invocation.getMethod(), java.util.Arrays.toString(invocation.getArguments())));
}
));
// Only mock test(true) and not test(false).
doReturn(1).when(mock).test(true);
Assert.assertEquals(mock.test(true), 1);
Assert.assertEquals(
// Expecting argument mismatch error
mock.test(false),
2
);
}
}
On execution we get:
java.lang.IllegalArgumentException: You cannot invoke public abstract int com.example.SomeInterface.test(java.lang.Boolean) with [false]

Related

Junit test case in spring boot

How can i write junit test for void method?
i have following method in service layer
#Override
public void add(Demo demo) throws ApiError {
if (!repository.existsByNameAndAge(demo.getName(), demo.getAge())) {
throw new ApiError(HttpStatus.BAD_REQUEST, "bad request");
}
Integer count = newRepository.countByName(cart.getName());
newRepository.save(new Demo(demo.getName(), demo.getAge(), demo.getCity(), count));
}
here is my service method and i want to do junit test case for it. but it's return type is void. i want to do testing of each statment. how can i done junit testing of this please suggest me..
Sorry I wrote the answer for Junit5 and then noticed you tagged Junit4, I will post it anyway, the idea is the same and differences in the code should be minor. What you can do is using Mockito to inject mocks and verify that the methods are called with the parameters you expect them be called. I would write 2 test cases: one to check the exception is thrown and the repositories are not called and another one to check the repository is properly saving:
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.InjectMocks;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.*;
#ExtendWith(MockitoExtension.class)
class MyServiceTest {
#Mock
private Repo repository;
#Mock
private NewRepo newRepository;
#Captor
private ArgumentCaptor<Demo> demoCaptor;
#InjectMocks
private MyService service;
#Test
void throwsIfDoesNotExistForGivenNameAndAge() {
when(repository.existsByNameAndAge("name", 12)).thenReturn(false);
assertThrows(ApiError.class, () -> service.add(new Demo("name", 12, "city", 10)));
verify(newRepository, times(0)).countByName(anyString());
verify(newRepository, times(0)).save(any(Demo.class));
}
#Test
void savesToNewRepositoryWithRightValues() {
when(repository.existsByNameAndAge("name", 12)).thenReturn(true);
when(newRepository.countByName("cart")).thenReturn(10);
service.add(new Demo("name", 12, "city", 10));
verify(newRepository, times(1)).save(demoCaptor.capture());
final Demo actual = captor.getValue();
final Demo expected = //create your expected here
assertEquals(expected, actual);
}
Remember to implement equals() and hashCode() in your Demo class, or another option could be asserting on the fields of Demo you care about. I'm also not sure what cart on which you are calling getName() is, but if it's another dependency of your service you will have to inject it as a mock and properly set it up with when() and return value.
The differences in terms of junit4/5 should be (not 100% sure it's all of them, going with my memory here):
the imports
the #ExtendWith should be #RunWith(mockitojunitrunner.class)
the test for the exception should be #Test(expected = ApiError.class) instead of using assertThrows
This function basically saves the data if the data is not available in the repository, Junits are meant to check if this function is working as expected. Here you will test for 2 cases
when data is available in the repository: For this mock repository.existsByNameAndAge(...) and return false
,in test case use expected #Test(expected=ApiError.class)
when it is not: In this case use opposite of the above case and don't use the expected attribute.

Exception while using PowerMockito with static method

I want to mock a static method using PowerMockito,
public class DepedencyService {
public static int getImportantValue() {
return -4;
}
}
public class Component {
public int componentMethod() {
return DepedencyService.getImportantValue();
}
}
but it is giving me an exception.
import static org.testng.Assert.assertEquals;
import org.easymock.EasyMock;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
#RunWith(PowerMockRunner.class)
#PrepareForTest(DepedencyService.class)
public class ComponentTest {
#Test
public void testComponentMethod() {
Component c = new Component();
PowerMockito.mockStatic(DepedencyService.class);
EasyMock.expect(DepedencyService.getImportantValue()).andReturn(1);
assertEquals(1, c.componentMethod());
}
}
The exception :-
java.lang.IllegalStateException: no last call on a mock available at
org.easymock.EasyMock.getControlForLastCall(EasyMock.java:520) at
org.easymock.EasyMock.expect(EasyMock.java:498)
Can anyone please help me? Why is this failing? I am new to PowerMockito and does not know what to do here!
Your main problem is that you're writing STUPID code (like most of us did at the beginning) where you rather should write SOLID code.
Using Powermock is just a surrender to this bad design.
Yes, classes having only static methods are called utility classes.
But you should get over this misconception that classes providing common behavior should have (only) static methods.
As a rule of thumb there should be only one non private static method in your entire program, and this is main().
You appear to be mixing mocking frameworks.
You need to properly arrange the static dependencies before exercising the test
Since PowerMockito is used to mock the static class then you should use Mockito to arrange the expected behavior
For example
#RunWith(PowerMockRunner.class)
#PrepareForTest(DepedencyService.class)
public class ComponentTest {
#Test
public void testComponentMethod() {
//Arrange
int expected = 1;
PowerMockito.mockStatic(DepedencyService.class);
Mockito.when(DepedencyService.getImportantValue()).thenReturn(expected);
Component subject = new Component();
//Act
int actual = subject.componentMethod();
//Assert
assertEquals(expected, actual);
}
}
That said, I would advise not having your code tightly coupled to static dependencies. It makes for difficult to test code.

Mocking object instantiation with new in Java using PowerMock not working

I am trying to Unit Test a class in Java.
Code for this class: ToBeTested
public class ToBeTested {
private Collaborator collaborator;
public ToBeTested() {
System.out.println("ToBeTested: Constructor");
}
public void start() {
System.out.println("ToBeTested: Start");
collaborator = new Collaborator();
}
}
This class ToBeTested depends on another class, Collaborator.
Code for class: Collaborator
public class Collaborator {
Collaborator() {
System.out.println("Collaborator: Constructor");
}
}
While testing the class ToBeTested, I want to stub instantiation of Collaborator. That's a dependency I want to mock and I don't want it's constructor to be called.
I'm using Junit (v4.12) and PowerMock (v1.6.1).
Code for Test Class: TestToBeTested
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.easymock.annotation.Mock;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import static org.powermock.api.easymock.PowerMock.*;
#RunWith(PowerMockRunner.class)
#PrepareForTest({ToBeTested.class, Collaborator.class})
public class TestToBeTested {
#Mock
private Collaborator collaborator;
private ToBeTested toBeTested;
#Before
public void setUp() throws Exception {
collaborator = createMock(Collaborator.class);
expectNew(collaborator.getClass()).andReturn(null);
toBeTested = new ToBeTested();
}
#Test
public void test() {
replayAll();
toBeTested.start();
verifyAll();
}
}
My understanding is that this will mock or stub out Collaborator and it's constructor should not be called. However, when I run the test, I notice that original constructor of Collaborator is called.
Output of test run:
ToBeTested: Constructor
ToBeTested: Start
Collaborator: Constructor
I'm very new to Java and Unit Testing in Java, so I apologize if I'm doing a very fundamental mistake here.
During my quest to find out the root cause, I have referred to following SO questions:
PowerMock's expectNew() isn't mocking a constructor as expected
PowerMock expectNew how to specify the type of the parameters
Not able to mock constructor using PowerMock
https://dzone.com/articles/using-powermock-mock
Thank you very much in advance for help/suggestions/feedback.
One possible reason that it might not be working could be this line:
expectNew(collaborator.getClass()).andReturn(null);
collaborator is a mocked instance which means it's "getClass()" method is going to return Collaborator$CGLIBMockedWithPowermock or something like that -- not the Collaborator class you want it to be. So you might get it to work simply by changing that line to:
expectNew(Collaborator.class).andReturn(null);

mockito, spy- not sure how it's done for partial mocking

I have a class, where i want to mock certain methods of the class and test the others. That is the only way i can verity and assert that it's working.
class UnderTest{
public void methodToTest(){
methodToCall1()
methodToCall2()
}
public void methodToCall1(){
}
public void methodToCall2(){
}
}
Now, since i want to test the first method, i want to create a partial mock of UnderTest so i can verify that those two methods were called.
How do i achieve this in Mockito?
Thanks for your help!
You mentioned you wanted to do two things:
1. Create real partial mocks
2. Verify method invocations
However, since your goal is to validate that methodToCall1() and methodToCall2() were actually invoked, all you need to do is spy on the real object. This can be accomplished with the following code block:
//Spy UnderTest and call methodToTest()
UnderTest mUnderTest = new UnderTest();
UnderTest spyUnderTest = Spy(mUnderTest);
spyUnderTest.methodToTest();
//Verify methodToCall1() and methodToCall2() were invoked
verify(spyUnderTest).methodToCall1();
verify(spyUnderTest).methodToCall2();
If one of the methods are not called, for example methodToCall1, an Exception will be thrown:
Exception in thread "main" Wanted but not invoked:
undertest.methodToCall1();
...
package foo;
import static org.mockito.Mockito.verify;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Spy;
import org.mockito.runners.MockitoJUnitRunner;
#RunWith(MockitoJUnitRunner.class)
public class FooTest {
#Spy
private UnderTest underTest;
#Test
public void whenMethodToTestExecutedThenMethods1And2AreCalled() {
// Act
underTest.methodToTest();
// Assert
verify(underTest).methodToCall1();
verify(underTest).methodToCall2();
}
}

passing Parameterized input using Mockitos

I am using Mockito for unit testing. I am wondering if its possible to send Parametrized input parameters with as in Junit testing
e.g
#InjectMocks
MockClass mockClass = new MockClass();
#Test
public void mockTestMethod()
{
mockClass.testMethod(stringInput);
// here I want to pass a list of String inputs
// this is possible in Junit through Parameterized.class..
// wondering if its can be done in Mockito
}
In JUnit, Parameterized tests use a special runner that ensure that the test is instantiated multiple times, so each test method is called multiple times. Mockito is a tool for writing specific unit tests, so there is no built-in ability to run the same test multiple times with different Mockito expectations.
If you want your test conditions to change, your best bet is to do one of the following:
Parameterize your test using JUnit, with a parameter for the mock inputs you want;
Run a loop of different parameters in your test, which unfortunately avoids the "test one thing per method" philosophy
Extract a method that actually performs the test, and create a new #Test method for each mock you want.
Note that there's no prohibition on using mock objects as #Parameterized test parameters. If you're looking to parameterize based on mocks, you can do that, possibly creating the mock and setting the expectations in a static method on the test.
Note about runners: This Parameterized test runner conflicts with Mockito's MockitoJUnitRunner: Each test class can only have one runner. You'll want to switch to #Before and #After methods or a Mockito JUnit4 rule for your setup, if you use them both.
As an example, compressed from a different answer that explains more about Parameterized runners versus JUnit rules and lifting from the JUnit4 Parameterized Test doc page and MockitoRule doc page:
#RunWith(Parameterized.class)
public class YourComponentTest {
#Rule public MockitoRule rule = MockitoJUnit.rule();
#Mock YourDep mockYourDep;
#Parameters public static Collection<Object[]> data() { /* Return the values */ }
public YourComponentTest(Parameter parameter) { /* Save the parameter to a field */ }
#Test public void test() { /* Use the field value in assertions */ }
}
If you are stuck with an older version of mockito where MockitoRule isn't available, the other possibility is to initialize the mocks explicitely with MockitoAnnotations.initMocks:
#RunWith(Parameterized.class)
public class YourComponentTest {
#Mock YourDep mockYourDep;
#Parameter
public Parameter parameter;
#Parameters public static Collection<Object[]> data() { /* Return the values */ }
#Before
public void init() {
MockitoAnnotations.initMocks(this);
}
#Test public void test() { /* Use the field value in assertions */ }
}
You can use the JUnitParamsRunner. Here's how I do it:
import junitparams.JUnitParamsRunner;
import junitparams.Parameters;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import java.util.Arrays;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
#RunWith(value = JUnitParamsRunner.class)
public class ParameterizedMockitoTest {
#InjectMocks
private SomeService someService;
#Mock
private SomeOtherService someOtherService;
#Before
public void setup() {
initMocks(this);
}
#Test
#Parameters(method = "getParameters")
public void testWithParameters(Boolean parameter, Boolean expected) throws Exception {
when(someOtherService.getSomething()).thenReturn(new Something());
Boolean testObject = someService.getTestObject(parameter);
assertThat(testObject, is(expected));
}
#Test
public void testSomeBasicStuffWithoutParameters() {
int i = 0;
assertThat(i, is(0));
}
public Iterable getParameters() {
return Arrays.asList(new Object[][]{
{Boolean.TRUE, Boolean.TRUE},
{Boolean.FALSE, Boolean.FALSE},
});
}
}
What solved it for me was:
Class level annotation of #ExtendWith(MockitoExtension.class)
Annotate each mock object with #Mock
#InjectMocks on the test class. Or a setup method annotated with #BeforeEach where you initialise the class to be tested.
if you need the #test annotation, make sure you import org.junit.jupiter.api.Test. org.junit.test will not work!
I'm using mockito version 4.

Categories