Reputation: 323
I'm trying to write unit test for my method called getBestSellers()
.
Here it is:
package bookstore.scraper.book.scrapingtypeservice;
import bookstore.scraper.enums.Bookstore;
import bookstore.scraper.book.Book;
import bookstore.scraper.fetcher.empik.EmpikFetchingBookService;
import bookstore.scraper.fetcher.merlin.MerlinFetchingBookService;
import bookstore.scraper.urlproperties.EmpikUrlProperties;
import bookstore.scraper.urlproperties.MerlinUrlProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import static bookstore.scraper.utilities.JSoupConnector.connect;
@Service
public class BestSellersService {
private final EmpikUrlProperties empikUrlProperties;
private final MerlinUrlProperties merlinUrlProperties;
private final EmpikFetchingBookService empikBookService;
private final MerlinFetchingBookService merlinBookService;
@Autowired
public BestSellersService(EmpikFetchingBookService empikBookService, MerlinFetchingBookService merlinBookService, EmpikUrlProperties empikUrlProperties, MerlinUrlProperties merlinUrlProperties) {
this.empikBookService = empikBookService;
this.merlinBookService = merlinBookService;
this.empikUrlProperties = empikUrlProperties;
this.merlinUrlProperties = merlinUrlProperties;
}
public Map<Bookstore, List<Book>> getBestSellers() {
Map<Bookstore, List<Book>> bookstoreWithBestSellers = new EnumMap<>(Bookstore.class);
bookstoreWithBestSellers.put(Bookstore.EMPIK, empikBookService
.get5BestSellersEmpik(connect(empikUrlProperties.getEmpik().getBestSellers())));
bookstoreWithBestSellers.put(Bookstore.MERLIN, merlinBookService
.get5BestSellersMerlin(connect(merlinUrlProperties.getMerlin().getBestSellers())));
return bookstoreWithBestSellers;
}
}
So, first I prepared test which looks like this:
package bookstore.scraper.book.scrapingtypeservice;
import bookstore.scraper.book.Book;
import bookstore.scraper.dataprovider.EmpikBookProvider;
import bookstore.scraper.dataprovider.MerlinBookProvider;
import bookstore.scraper.enums.Bookstore;
import bookstore.scraper.fetcher.empik.EmpikFetchingBookService;
import bookstore.scraper.fetcher.merlin.MerlinFetchingBookService;
import bookstore.scraper.urlproperties.EmpikUrlProperties;
import bookstore.scraper.urlproperties.MerlinUrlProperties;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.List;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class BestSellersServiceTest {
@Mock
private EmpikFetchingBookService empikBookService;
@Mock
private MerlinFetchingBookService merlinBookService;
@Mock
private EmpikUrlProperties empikUrlProperties;
@Mock
private MerlinUrlProperties merlinUrlProperties;
@InjectMocks
private BestSellersService bestSellersService;
@Test
public void getBestSellers() {
List<Book> merlinBestsellers = EmpikBookProvider.prepare5Bestsellers();
List<Book> empikBestsellers = MerlinBookProvider.prepare5Bestsellers();
when(empikBookService.get5BestSellersEmpik(any())).thenReturn(empikBestsellers);
when(merlinBookService.get5BestSellersMerlin(any())).thenReturn(merlinBestsellers);
//when(empikUrlProperties.getEmpik().getBestSellers()).thenReturn(anyString());
//when(merlinUrlProperties.getMerlin().getBestSellers()).thenReturn(anyString());
Map<Bookstore, List<Book>> actualMap = bestSellersService.getBestSellers();
Map<Bookstore, List<Book>> expectedMap = null;
assertEquals(expectedMap, actualMap);
assertThat(actualMap).hasSize(expectedMap.size());
}
}
Without setting behaviour for properties classes as I thought it is unnecessary, because I put any()
when calling empikBookService.get5BestSellersEmpik
(same for merlinBookService
) but it threw NPE when calling
bookstoreWithBestSellers.put(Bookstore.EMPIK, empikBookService
.get5BestSellersEmpik(connect(empikUrlProperties.getEmpik().getBestSellers())));
I've debugged it and I have seen that
empikUrlProperties.getEmpik().getBestSellers()))
was given me NPE. So I set behaviour like this:
when(empikUrlProperties.getEmpik().getBestSellers()).thenReturn(anyString());
when(merlinUrlProperties.getMerlin().getBestSellers()).thenReturn(anyString());
and now it is giving me here NPE with stactrace:
ava.lang.NullPointerException
at bookstore.scraper.book.scrapingtypeservice.BestSellersServiceTest.getBestSellers(BestSellersServiceTest.java:48)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
.
.
.
connect
method used in tested method:
@UtilityClass
public class JSoupConnector {
public static Document connect(String url) {
try {
return Jsoup.connect(url).get();
} catch (IOException e) {
throw new IllegalArgumentException("Cannot connect to" + url);
}
}
}
Properties class (same for merlin)
package bookstore.scraper.urlproperties;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Getter
@Setter
@Component
@ConfigurationProperties("external.library.url")
public class EmpikUrlProperties {
private Empik empik = new Empik();
@Getter
@Setter
public static class Empik {
private String mostPreciseBook;
private String bestSellers;
private String concreteBook;
private String romances;
private String biographies;
private String crime;
private String guides;
private String fantasy;
}
}
What am I making wrong? Why it didnt work in the first place when I put any()
Upvotes: 0
Views: 1015
Reputation: 5034
When you set your mock :
when(empikUrlProperties.getEmpik().getBestSellers()).thenReturn(anyString());
You have mocked empikUrlProperties
, which is great, BUT you have not told that mock what to do when getEmpik()
is called on it. Consequently, that method call will return null both here in the test, and also (before you had this line) in the production code - so this is the cause of your NPE when getBestSellers()
is called.
Consequently, set that mock up, something like :
@Mock
private EmpikUrlProperties empikUrlProperties;
@Mock
private EmpikUrlProperties.Empik empikMock;
when(empikUrlProperties.getEmpik()).thenReturn(empikMock);
when(empikMock.getBestSellers()).thenReturn(anyString());
Upvotes: 1
Reputation: 692003
There are many problems, but the main one is that you're misunderstanding how mocking, argument evaluation and any()
work.
You're using
when(empikBookService.get5BestSellersEmpik(any())).thenReturn(empikBestsellers);
This tells the mock empikBookService than whenever its get5BestSellersEmpik
method is called, it should return empikBestsellers
, whetever the argument passed to the method is.
What does your actual code pass as argument when executing your test? It passes the value returned by
connect(empikUrlProperties.getEmpik().getBestSellers())
The key part is that this expression is evaluated first, and then its result is passed as argument to the get5BestSellersEmpik()
method.
Just like when you do
System.out.println(a + b)
a + b
is first evaluated. If the result is 42, then the value 42 is passed to println(), and println prints 42.
So, in order for your test not to fail, the expression
connect(empikUrlProperties.getEmpik().getBestSellers())
must be evaluated successfully. Its result doesn't matter, since you've configure your mock to accept any argument. But that's irrelevant.
You're trying to do
when(empikUrlProperties.getEmpik().getBestSellers()).thenReturn(anyString());
That doesn't make any sense.
First, because empikUrlProperties.getEmpik()
will return null, since empikUrlProperties
is a mock, and mocks return null by default. And null.getBestSellers()
will thus cause a NullPointerException.
Second, because telling a mock that it should return any string doesn't make sense. If you don't care about the string it should return, then choose a string by yourself, and make it return that. anyString()
actually returns null, so you're telling it to return null.
So you need to fix that. Always think about what your code is doing instead of trying to apply a recipe.
And finally, your test also calls connect(...)
, which is a staic method, that you haven't (and can't) mock. This method will be called too. And it tries to connect to an actual URL. So if nothing is responding during your test at that URL, that won't work either. This connect()
method should really be part of a dependency of your service, and this dependency should be mocked.
Upvotes: 1