I don't know how to test a service with Mockito if it has two dependencies and the second dependency should work with the result of the first.
To better describe my problem, I wrote a small application for this: https://github.com/MartinHein-dev/mockito-example
With http://localhost:8080/countries one gets the result of three countries from https://restcountries.com/
I would be very happy if you could show me how the unit tests for de.example.mockito.service.CountryService.class would look like.
It feels wrong to continue with the mocked result of this.restCountiesClient.findCountriesByCode(countryCodes) and use it as a parameter in this.countryMapper.map(restCountryList), whose result is also mocked.
#ExtendWith(MockitoExtension.class)
class CountryServiceTest {
#Mock
RestCountriesClient client;
#Mock
CountryMapper mapper;
CountryService countryService;
List<RestCountry> restCountryList;
List<CountryDto> countryDtoList;
final String COUNTRY_CODES = "pe,at";
#BeforeEach
void setUp() throws Exception {
countryService = new CountryService(client, mapper);
restCountryList = List.of(
new RestCountry(new RestCountryName("Peru", "Republic of Peru")),
new RestCountry(new RestCountryName("Austria", "Republic of Austria"))
);
countryDtoList = List.of(
new CountryDto("Peru", "Republic of Peru"),
new CountryDto("Austria", "Republic of Austria")
);
}
#Test
void getAllCountries() {
given(client.findCountriesByCode(COUNTRY_CODES)).willReturn(restCountryList);
given(mapper.map(restCountryList)).willReturn(countryDtoList);
List<CountryDto> result = this.countryService.getAllCountries(COUNTRY_CODES);
assertEquals(2, result.size());
assertEquals("Peru", result.get(0).getCommonName());
assertEquals("Republic of Peru", result.get(0).getOfficialName());
assertEquals("Austria", result.get(1).getCommonName());
assertEquals("Republic of Austria", result.get(1).getOfficialName());
}
#Test
void getAllCountries2() {
given(client.findCountriesByCode(COUNTRY_CODES)).willReturn(restCountryList);
given(mapper.map(restCountryList)).willReturn(countryDtoList);
List<CountryDto> result = this.countryService.getAllCountries2(COUNTRY_CODES);
assertEquals(2, result.size());
assertEquals("Peru", result.get(0).getCommonName());
assertEquals("Republic of Peru", result.get(0).getOfficialName());
assertEquals("Austria", result.get(1).getCommonName());
assertEquals("Republic of Austria", result.get(1).getOfficialName());
}
#AfterEach
void tearDown() throws Exception {
reset(client, mapper);
}
Updated tests (2):
#Test
void getAllCountries() {
given(client.findCountriesByCode(COUNTRY_CODES)).willReturn(restCountryList);
given(mapper.map(restCountryList)).willReturn(countryDtoList);
this.countryService.getAllCountries(COUNTRY_CODES);
verify(client, times(1)).findCountriesByCode(COUNTRY_CODES);
verify(mapper, times(1)).map(restCountryList);
}
#Test
void getAllCountries2() {
given(client.findCountriesByCode(COUNTRY_CODES)).willReturn(restCountryList);
given(mapper.map(restCountryList)).willReturn(countryDtoList);
List<CountryDto> result = this.countryService.getAllCountries2(COUNTRY_CODES);
assertEquals("Other Name", restCountryList.get(0).getName().getCommon());
verify(client, times(1)).findCountriesByCode(COUNTRY_CODES);
verify(mapper, times(1)).map(restCountryList);
}
When you are testing getAllCountries you are testing that the data flow works within the method, because the method itself doesn't do anything else but pass data between the dependencies. Therefore you do not need to set up populated objects for that method and you do not need to assert that the returned objects contain any particular populated data. You only need to verify that the expected (mocked) dependencies were called with the expected object references.
Testing the values you set up in th test belong to the unit test that target CountryMapper.
For getAllCountries2 you would have to verify that the value in the test data has changed like you expect, but again no need to verify the values otherwise.
Related
I need to create a Junit test for a class in an AEM project and I'm having NullPointerException problems:
I create the ClassTestImpl
#ExtendWith({AemContextExtension.class, MockitoExtension.class})
class TestImpl {
private final AemContext ctx = new AemContext();
#Mock
private Test test;
#Mock
private ModelFactory modelFactory;
#BeforeEach
void setUp() throws Exception {
ctx.addModelsForClasses(TestImpl.class);
ctx.load().json("/com/project/core/models/adobe/TestImplTest.json","/content");
lenient().when(modelFactory.getModelFromWrappedRequest(eq(ctx.request()),
any(Resource.class), eq(Test.class)))
.thenReturn(test);
}
#Test
void testGetText() {
final String expected = "textTEST";
ctx.currentResource("/content/text");
Test test = ctx.request().adaptTo(Test.class);
String actual = test.getText();
assertEquals(expected,actual);
}
and the json structure:
"text": {
"jcr:primaryType": "nt:unstructured",
"sling:resourceType": "project/components/core/title",
"text": "textTEST"
}
}
when i Run test i give that result:
#Test
void testGetText() {
final String expected = "titleTEST";
ctx.currentResource("/content/title");
Title title = ctx.request().adaptTo(Title.class);
-->String actual = title[NullPointerException].getText();<--
assertEquals(expected,actual);
}
It looks like your model is a null reference. You do try to mock it with MockitoExtension but that's largely superfluous, given that you're also using AemContextExtension and it's probably the cause of the issue.
Null pointers aside, this code doesn't even test anything. Everything is mocked, even the Test class which I understand to be the subject under test.
Also, the parameter you're passing to addModelsForClasses looks like the test class (TestImpl) rather than the class of the Sling Model Test.
Instead of relying on Mockito, let the AEM Mocks library set up all the underlying objects by itself and make sure the class you're testing is the real thing, rather than a mock.
#ExtendWith(AemContextExtension.class)
class TestImpl {
private final AemContext ctx = new AemContext();
#BeforeEach
void setUp() throws Exception {
ctx.addModelsForClasses(Test.class); // Give it the Sling Model
ctx.load().json("/com/project/core/models/adobe/TestImplTest.json","/content");
}
#Test
void testGetText() {
final String expected = "textTEST";
ctx.currentResource("/content/text");
Test test = ctx.request().adaptTo(Test.class); // It'll use the actual class, not a mock this way
String actual = test.getText();
assertEquals(expected,actual);
}
}
See
https://sling.apache.org/documentation/development/sling-mock.html#sling-models-1
https://wcm.io/testing/aem-mock/usage-content-loader-builder.html
I try to mock same method calls with different collection-arguments.
My problem is that im not getting the correct mocked-answer from Mocked-Call for the input.
Test-Class:
#ExtendWith(SpringExtension.class)
public class CollectionTest {
#MockBean
private Controller c;
#BeforeEach
public void init() {
Collection<String> a = Mockito.anyCollection();
a.add("a");
Mockito.when(c.run(a)).thenReturn("a");
Collection<String> b = Mockito.anyCollection();
b.add("b");
Mockito.when(c.run(b)).thenReturn("b");
}
#Test
public void test() {
assertEquals("a", c.run(Lists.newArrayList("a"))); // DOESNT'WORK!!! Returns "b" but should "a"
assertEquals("b", c.run(Lists.newArrayList("b"))); //
}
}
Controller-Class:
#Service
public class Controller{
public String run(Collection<String> c) {
return "not-mocked";
}
}
I'v got no idea why it doesn't return "a". I tried to change the collection to string but same behaviour.
What are the Steps to do, to get the following behaviour?
#Test
public void test() {
assertEquals("a", c.run(Lists.newArrayList("a"))); // should return "a"
assertEquals("b", c.run(Lists.newArrayList("b"))); // should return "b"
}
Im using Java Mockito "3.1" and Spring, but I think Mockito is the important information here.
Your second call - Mockito.when(c.run(b)).thenReturn("b");
is overruling our first call so Mockito will therefore always return "b".
If you need multiple answers from the same call, you can use the varags variant:
when(c.run(anyCollection())).thenReturn("a", "b");
Now the first call to the controller's run method will return "a" and all subsequent calls will return "b". You can provide as many return results as you want and the last one will be repeated from then on as the answer.
Write two tests will show you the results you are expecting.
You are adding to the same Controller two different results so you get only the last one : Mockito.when(c.run(b)).thenReturn("b");
Normal. The last mocked expected result in your setUp() will stay in memory.
Previous answer was :
You can use something like junit and mockito to test your spring-web-mvc application.
It looks like that :
#WebMvcTest(controllers = UserController.class)
#ActiveProfiles("test")
class UserControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private UserService userService;
private List<User> userList;
#BeforeEach
void setUp() {
this.userList = new ArrayList<>();
this.userList.add(new User(1L, "user1#gmail.com", "pwd1","User1"));
this.userList.add(new User(2L, "user2#gmail.com", "pwd2","User2"));
this.userList.add(new User(3L, "user3#gmail.com", "pwd3","User3"));
}
}
And as an example :
#Test
void shouldFetchAllUsers() throws Exception {
given(userService.findAllUsers()).willReturn(userList);
this.mockMvc.perform(get("/api/users"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.size()", is(userList.size() )));
}
Example from #see https://medium.com/backend-habit/integrate-junit-and-mockito-unit-testing-for-controller-layer-91bb4099c2a5
I am writing some test code for a processElement function in Apache Flink 1.4:
public class ProcessFunctionClass {
public void processElement(Tuple2<String, String> tuple2, Context context, Collector<Tuple2<String, String>> collector) {
// if the state is empty, start a timer
if (listState.get().iterator().hasNext() == false)
context.timerService().registerEventTimeTimer(1000);
listState.add("someStringToBeStored");
// ...
}
}
public class ProcessFunctionClassTest {
private ProcessFunctionClass processFunctionClass;
#Mock
private ListState<String> listState;
#Before
public void setUp() throws Exception {
processFunctionClass = new ProcessFunctionClass();
}
#Test
public void testProcessElement() {
ListState mockListState = mock(ListState.class);
Iterable mockIterable = mock(Iterable.class);
Iterator mockIterator = mock(Iterator.class);
MockitoAnnotations.initMocks(this);
when(tDPListState.get()).thenReturn(mockIterable);
when(tDPListState.get().iterator()).thenReturn(mockIterator);
when(tDPListState.get().iterator().hasNext()).thenReturn(false);
processFunctionClass.processElement(tuple2, context, collector);
// verify(...)
}
}
When I debug using my IDE, just before I step into the processElement() method, listState is not null and appears to have been mocked successfully, but as soon as I get to listState.get().iterator().hasNext(), listState is null and I get a NullPointerException. What am I doing wrong here?
In ProcessFunctionClass you have a private listState variable.
In your test you create a completely unrelated mockListState variable and set some expectations on it.
For your test to work, you must provide a way (constructor or setter) to set ProcessFunctionClass.listState to desired value (your mocked list state)
On top of that, MockitoAnnotations.initMocks(this); seems to do nothing in your example: you haven't shown us any fields annotated with #Mock or #InjectMocks
Update
You are misusing #Mock annotation.
You should place it in the test class, not in class under test.
When placed in the test class, after a call to initMocks, the filed will be initialized with a mock of an appropriate type.
What you should fo instead:
remove MockitoAnnotations.initMocks(this);, you are creating all the mocks manually.
add a constructor in ProcessFunctionClass
public ProcessFunctionClass(ListState<String> listState) {
this.listState = listState
}
use this constructor in your test
var mockListState = mock(ListState.class);
var processFunctionClass = new ProcessFunctionClass();
It's useful to test exception handling. In this specific case, I have a extractor that will do a specific task when an exception is thrown while unmarshaling a specific class.
Example Code
Below is a simplified example of the code. The production version is much more complicated.
public class Example {
public static enum EntryType {
TYPE_1,
TYPE_2
}
public static class Thing {
List<String> data = new ArrayList<String>();
EnumSet<EntryType> failedConversions = EnumSet.noneOf(EntryType.class);
}
public static class MyHelper {
public String unmarshal(String input) throws UnmarshalException {
// pretend this does more complicated stuff
return input + " foo ";
}
}
public static class MyService {
MyHelper adapter = new MyHelper();
public Thing process() {
Thing processed = new Thing();
try {
adapter.unmarshal("Type 1");
} catch (UnmarshalException e) {
processed.failedConversions.add(EntryType.TYPE_1);
}
// do some stuff
try {
adapter.unmarshal("Type 2");
} catch (UnmarshalException e) {
processed.failedConversions.add(EntryType.TYPE_2);
}
return processed;
}
}
}
Things I've Tried
Here's a list of things I've tried. For brevity, I haven't filled in all the mundane details.
Spying
The following method doesn't do anything and the exception doesn't throw. I'm not sure why.
#Test
public void shouldFlagFailedConversionUsingSpy()
throws Exception {
MyHelper spied = spy(fixture.adapter);
doThrow(new UnmarshalException("foo")).when(spied).unmarshal(
Mockito.eq("Type 1"));
Thing actual = fixture.process();
assertEquals(1, actual.failedConversions.size());
assertThat(actual.failedConversions.contains(EntryType.TYPE_1), is(true));
}
Mocking
The following didn't work because partial mocks don't seem to play well with methods that throw exceptions.
#Test
public void shouldFlagFailedConversionUsingMocks()
throws Exception {
MyHelper mockAdapter = mock(MyHelper.class);
when(mockAdapter.unmarshal(Mockito.anyString())).thenCallRealMethod();
when(mockAdapter.unmarshal(Mockito.eq("Type 2"))).thenThrow(
new UnmarshalException("foo"));
Thing actual = fixture.process();
assertEquals(1, actual.failedConversions.size());
assertThat(actual.failedConversions.contains(EntryType.TYPE_2), is(true));
}
ThenAnswer
This works, but I'm not sure if it's the proper way to do this:
#Test
public void shouldFlagFailedConversionUsingThenAnswer() throws Exception {
final MyHelper realAdapter = new MyHelper();
MyHelper mockAdapter = mock(MyHelper.class);
fixture.adapter = mockAdapter;
when(mockAdapter.unmarshal(Mockito.anyString())).then(
new Answer<String>() {
#Override
public String answer(InvocationOnMock invocation)
throws Throwable {
Object[] args = invocation.getArguments();
String input = (String) args[0];
if (input.equals("Type 1")) {
throw new UnmarshalException("foo");
}
return realAdapter.unmarshal(input);
}
});
Thing actual = fixture.process();
assertEquals(1, actual.failedConversions.size());
assertThat(actual.failedConversions.contains(EntryType.TYPE_1), is(true));
}
Question
Although the thenAnswer method works, it doesn't seem to be the proper solution. What is the correct way to perform a partial mock for this situation?
I'm not quite sure what you were getting at with the mocking and the spying, but you only really need to mock here.
First, I ran into a few snags when trying your mocks out for whatever reason. I believe this had to do with the spy call which was messed up in some way. I did eventually overcome these, but I wanted to get something simple to pass.
Next, I did notice something off with the way you were spying (the basis of my approach):
MyHelper spied = spy(fixture.adapter);
This implies that you want an instance of MyHelper mocked out, not spied. The worst part is that even if this object were fully hydrated, it wouldn't be properly injected since you haven't reassigned it to the test object (which I presume is fixture).
My preference is to use the MockitoJUnitRunner to help with the injection of mocked instances, and from there I build up a basis of what it is I actually need to mock.
There's only one mocked instance and then the test object, and this declaration will ensure that they're both instantiated and injected:
#RunWith(MockitoJUnitRunner.class)
public class ExampleTest {
#Mock
private MyHelper adapter;
#InjectMocks
private MyService fixture;
}
The idea is that you're injecting your mock into the fixture. You don't have to use this - you could use standard setters in a #Before declaration, but I prefer this since it greatly reduces the boilerplate code you have to write to get mocking to work.
Now there's only one change to be made: remove the spy instance and replace its previous usage with the actual mock.
doThrow(new UnmarshalException("foo")).when(adapter).unmarshal(eq("Type 1"));
With all of the code hoisted, this passes:
#RunWith(MockitoJUnitRunner.class)
public class ExampleTest {
#Mock
private MyHelper adapter;
#InjectMocks
private MyService fixture;
#Test
public void shouldFlagFailedConversionUsingSpy()
throws Exception {
doThrow(new UnmarshalException("foo")).when(adapter).unmarshal(eq("Type 1"));
Thing actual = fixture.process();
assertEquals(1, actual.failedConversions.size());
assertThat(actual.failedConversions.contains(Example.EntryType.TYPE_1), is(true));
}
}
Not being one to want to leave the question/use case incomplete, I circled around and replaced the test with the inner classes, and it works fine too:
#RunWith(MockitoJUnitRunner.class)
public class ExampleTest {
#Mock
private Example.MyHelper adapter;
#InjectMocks
private Example.MyService fixture;
#Test
public void shouldFlagFailedConversionUsingSpy()
throws Exception {
doThrow(new UnmarshalException("foo")).when(adapter).unmarshal(eq("Type 1"));
Example.Thing actual = fixture.process();
assertEquals(1, actual.failedConversions.size());
assertThat(actual.failedConversions.contains(Example.EntryType.TYPE_1), is(true));
}
}
I want to test a JSF Backing-Bean method "isInProgress" that delegates to a service method "isInProgress". When the service method throws an exception, the bean should put an event on a specific event logger and return false.
When I debug the following test, I get into the catch-block. The mocked service does not throw the exception, but returns a "default answer" which is false for th boolean. What am I doing wrong?
I also wonder if the try-catch around the "when" call can be avoided somehow, as the actual exception is swallowed by the bean under test. In fact I think "declaratively" passing the name of the method to the "when" should suffice. Is there a way to get that cleaner ?
#Test
public void testIsInProgressExeption() {
//prepare object and inputs
MyBean bean = new MyBean();
MyService service = mock(MyAdapterService.class);
bean.setService(service);
try {
when(bean.getService().isInProgress()).thenThrow(new Exception());
} catch (Exception e) {
//prepare expected object and result
MyBean expectedBean = new MyBean();
expectedBean.setService(service);
boolean expected = false;
//execute method under test
boolean actual = bean.isInProgress();
//check return values and exceptions
assertEquals(expected, actual);
//check that bean did not change unexpectedly
assertTrue(bean.equals(expectedBean));
//check sideeffects on event log
assertTrue(logEvents.containsMessage("MDI09"));
}
}
For reference here is the updated Test:
#Test
public void testIsInProgressExeption() throws Exception {
//prepare object and inputs
MyBean bean = new MyBean();
MyService service = mock(MyAdapterService.class);
bean.setService(service);
when(bean.getService().isInProgress()).thenThrow(new Exception());
//prepare expected object and result
MyBean expectedBean = new MyBean();
expectedBean.setService(service);
boolean expected = false;
//execute method under test
boolean actual = bean.isInProgress();
//check return values and exceptions
assertEquals(expected, actual);
//check that bean did not change unexpectedly
assertTrue(bean.equals(expectedBean));
//check sideeffects on event log
assertTrue(logEvents.containsMessage("MDI09"));
}
Move the when clause out of the try block and change it to:
when(service.isInProgress()).thenThrow(new Exception());
Now it should throw an exception when called.
For the records, I was doing state-base testing. Interestingly, Fowler posted in http://martinfowler.com/articles/mocksArentStubs.html a very nice article that goes quite the same route but then differentiates it from mocking and interaction-based testing.
You are doing it wrong. First you should lay out your test with the BDD or AAA keywords, with BDD :
#Test public void testIsInProgressExeption() {
// given
// when
// then
}
In the given part you will write your fixture, i.e. the setup of your test scenario. In the when part you will call the production code i.e. the tested subject. Lastly in the when part you will write your verifications and or assertions.
Stubs go in the fixture, so this line is misplaced, it doesn't belong here, it' just a definition of the behavior.
when(bean.getService().isInProgress()).thenThrow(new Exception());
However you should directly the service reference instead of the bean.getService(), this is akward.
I don't really understand why you are creating a new instance of the bean in the catch clause, this is weird. But here's how I wold write the test. Note by the way I explain in the unit test name what behavior the test is actully testing, writing this in camel case is way to painful to read, so I use the underscored convention, it's ok in tests.
#Test public void when_service_throw_Exception_InProgress_then_returns_false() throws Exception {
// given
MyBean bean = new MyBean();
MyService service = mock(MyAdapterService.class);
bean.setService(service);
when(service.isInProgress()).thenThrow(new Exception());
// when
boolean result = bean.isInProgress();
// then
assertFalse(result);
}
Also I would split the assertion on the event, this a different behavior :
#Test public void when_service_throw_Exception_InProgress_then_log_event_MDI09() throws Exception {
// given
MyBean bean = new MyBean();
MyService service = mock(MyAdapterService.class);
bean.setService(service);
// somehow set up the logEvents collaborator
when(service.isInProgress()).thenThrow(new Exception());
// when
bean.isInProgress();
// then
assertTrue(logEvents.containsMessage("MDI09"));
}
You can even go further to simplify the fixture, if you use JUnit, you write this code :
#RunWith(MockitoJUnitRunner.class)
public class MyBeanTest {
#Mock MyService service;
#Mock LogEvents logEvents;
#InjectMocks MyBean bean;
#Test public void when_service_throw_Exception_InProgress_then_log_event_MDI09() throws Exception {
// given
when(service.isInProgress()).thenThrow(Exception.class);
// when
bean.isInProgress();
// then
verify(logEvents).logEvent("MDI09");
}
}
In the example above I also extrapolated on the log event stuff, but it is just to give the idea of what's possible.