I mock articleElementSelector.getTag() to return a string and use InjectMocks annotation to inject the mock into the constructor. In the debugger, I see that articleElementSelector is mocked (because of the CGLib stuff), but when getTag() is invoked, it returns null.
HomePageScraperTest:
public class HomePageScraperTest extends UnitTest {
private static final String ARTICLE_TAG = "article";
private static final String URL_HOME_ARTICLE_1 = "http://www.home1.com";
private static final String URL_HOME_ARTICLE_2 = "http://www.home2.com";
#InjectMocks
private HomePageScraper homePageScraper;
#Mock
private JsoupParser jsoupParser;
#Mock
private ArticleScraper articleScraper;
#Mock
private ArticleElementSelector articleElementSelector;
#Mock
private Document homeDocument;
#Mock
private Element element1;
#Mock
private Element element2;
#Mock
private Elements elements1;
#Mock
private Elements elements2;
private URL homeUrl;
private Elements homeArticleElements = new Elements();
private Article homeArticle1;
private Article homeArticle2;
#Before
public void setUp() throws Exception {
homeUrl = new URL(URL_HOME_ARTICLE_1);
homeArticleElements.addAll(asList(element1, element2));
homeArticle1 = anArticle().withTitle("article1").build();
homeArticle2 = anArticle().withTitle("article2").build();
}
#Test
public void scrape() {
HomePage homePage = new HomePage(homeUrl);
when(articleElementSelector.getTag()).thenReturn(ARTICLE_TAG);
when(jsoupParser.parse(homeUrl)).thenReturn(homeDocument);
when(homeDocument.select(ARTICLE_TAG)).thenReturn(homeArticleElements);
when(element1.select("a")).thenReturn(elements1);
when(elements1.attr("href")).thenReturn(URL_HOME_ARTICLE_1);
when(element2.select("a")).thenReturn(elements2);
when(elements2.attr("href")).thenReturn(URL_HOME_ARTICLE_2);
when(articleScraper.scrape(URL_HOME_ARTICLE_1)).thenReturn(homeArticle1);
when(articleScraper.scrape(URL_HOME_ARTICLE_2)).thenReturn(homeArticle2);
List<Article> articles = homePageScraper.scrape(homePage);
assertThat(articles).containsOnly(homeArticle1, homeArticle2);
}
}
HomePageScraper (only relevant code)
#Component
public class HomePageScraper extends AbstractPageScraper {
private static final int HEADLINER_COUNT = 5;
public HomePageScraper(JsoupParser parser, ArticleElementSelector articleElementSelector, ArticleScraper articleScraper) {
super(parser, articleElementSelector, articleScraper);
}
}
AbstractPageScraper
public abstract class AbstractPageScraper {
private final String ARTICLE_TAG;
private JsoupParser parser;
ArticleScraper articleScraper;
public AbstractPageScraper(JsoupParser parser, ArticleElementSelector articleElementSelector, ArticleScraper articleScraper) {
this.parser = parser;
ARTICLE_TAG = articleElementSelector.getTag(); // here the mock returns null
this.articleScraper = articleScraper;
}
}
The test worked before but after I refactored it, pulling up duplicate code in the abstract class, I bumped on this one.
Related
Still playing around and trying to understand the "how" of Spring's Webflux and Reactor.
The following successfully adds a new DemoPOJO to the repo when the annotated controller is used (i.e., POST issued at //localhost:8080/v1/DemoPOJO).
However, when issuing the same POST using the router/handler implementation (i.e., //localhost:8080/v2/DemoPOJO), request.bodyToMono(DemoPOJO.class) does not appear to retrieve the DemoPOJO instance from the ServerRequest (i.e., DemoPOJO.printme() is not being invoked).
I'm "working on this", but thought I'd see if anyone can help me "get there faster". For-what-it's-worth, the router/handler implementations (i.e., GET) that don't require getting a DemoPOJO out of ServerRequest are working.
RESTful endpoints using annotation...
#RestController
public class DemoPOJOController {
private Logger logger = LoggerFactory.getLogger(DemoPOJOHandler.class);
#Autowired
DemoPOJOService service;
#RequestMapping(method = POST, value = "/v1/DemoPOJO")
public Mono<Boolean> addDemoPOJO(#RequestBody DemoPOJO demoPOJO) {
logger.debug("DemoPOJOController.addDemoPOJO( {} )", demoPOJO.getId());
return service.add(demoPOJO);
}
}
"Router" part of the corresponding router/handler implementation...
#Configuration
public class DemoPOJORouter {
private Logger logger = LoggerFactory.getLogger(DemoPOJOHandler.class);
#Bean
public RouterFunction<ServerResponse> route(DemoPOJOHandler requestHandler) {
logger.debug("DemoPOJORouter.route( DemoPOJOHandler )");
return nest(path("/v2"),
nest(accept(APPLICATION_JSON),
RouterFunctions.route(RequestPredicates.POST("/DemoPOJO"), requestHandler::add)));
}
}
"Handler" part of the router/handler implementation...
#Component
public class DemoPOJOHandler {
public static final String PATH_VAR_ID = "id";
private Logger logger = LoggerFactory.getLogger(DemoPOJOHandler.class);
#Autowired
private DemoPOJOService service;
public Mono<ServerResponse> add(ServerRequest request) {
logger.debug("DemoPOJOHandler.add( ServerRequest )");
request.bodyToMono(DemoPOJO.class).doOnSuccess(DemoPOJO::printMe);
return ServerResponse.ok().build();
}
}
DemoPOJORepo implementation (hoping to simplify my learning experience by avoiding a "real" repository)...
#Component
public class DemoPOJORepo {
private static final int NUM_OBJS = 15;
private Logger logger = LoggerFactory.getLogger(DemoPOJORepo.class);
private static DemoPOJORepo demoRepo = null;
private Map<Integer, DemoPOJO> demoPOJOMap;
private DemoPOJORepo() {
logger.debug("DemoPOJORepo.DemoPOJORepo()");
initMap();
}
public boolean add(DemoPOJO demoPOJO) {
logger.debug("DemoPOJORepo.add( DemoPOJO )");
boolean pojoAdded = false;
if (!demoPOJOMap.containsKey(demoPOJO.getId())) {
logger.debug("DemoPOJORepo.add( DemoPOJO ) -> adding for id {}", demoPOJO.getId());
demoPOJOMap.put(demoPOJO.getId(), demoPOJO);
pojoAdded = true;
}
return pojoAdded;
}
private void initMap() {
logger.debug("DemoPOJORepo.initMap()");
demoPOJOMap = new TreeMap<Integer, DemoPOJO>();
for (int ndx = 1; ndx < (NUM_OBJS + 1); ndx++) {
demoPOJOMap.put(ndx, new DemoPOJO(ndx, "foo_" + ndx, ndx + 100));
}
}
}
The objects being manipulated...
public class DemoPOJO {
private Logger logger = LoggerFactory.getLogger(DemoPOJOHandler.class);
public static final String DEF_NAME = "DEFAULT NAME";
public static final int DEF_VALUE = 99;
private int id;
private String name;
private int value;
public DemoPOJO(int id) {
this(id, DEF_NAME, DEF_VALUE);
}
public DemoPOJO(#JsonProperty("id") int id, #JsonProperty("name") String name, #JsonProperty("value") int value) {
logger.debug("DemoPOJO.DemoPOJO( {}, {}, {} )", id, name, value);
this.id = id;
this.name = name;
this.value = value;
}
// getters and setters go here
public void printMe() {
logger.debug("DemoPOJO.printMe()");
System.out.printf("id->%d, name->%s, value->%d%n", id, name, value);
}
}
i am guesstimating here since i am writing from mobile. But i think this is your problem.
request.bodyToMono(DemoPOJO.class).doOnSuccess(DemoPOJO::printMe);
return ServerResponse.ok().build();
You are thinking imperative, that first row will be executed then the second which is not the case in webflux. You have to think events-callbacks.
return request.bodyToMono(DemoPOJO.class)
.doOnSuccess(DemoPOJO::printMe)
.thenReturn(ServerResponse.ok().build());
I think this is it but i could be wrong.
I tried mocking the JdbcTemplate jdbcTemplate ,but that didn't cover whatever is inside
new Employee(.......);
Please let me know is there any way to cover those lines inside new Employee(...)?
public List<Employee> findByCustIdAndType(long Id, String type)
{
return jdbcTemplate.query(SQL.getEmployee(Id, type),
(rs, rowNum) -> new Employee(rs.getLong("CUSTOMER_ID"),
rs.getLong("ANCHOR_CUSTOMER_ID") ,
rs.getString("SEGMENT"),
rs.getDate("END_TS")));
}
Try using Mockito to capture the lambda, which is a RowMapper<Employee>. Then invoke it with a mock ResultSet set up to return the expected values so the returned Employee can be asserted. Here's an example:
#RunWith(MockitoJUnitRunner.class)
public class EmployeeDAOTest {
private static final long CUSTOMER_ID = 1;
private static final long ANCHOR_CUSTOMER_ID = 2;
private static final String SEGMENT = "A";
private static final Date END_TS = Date.valueOf(LocalDate.now());
#InjectMocks
private EmployeeDAO dao;
#Mock
private JdbcTemplate jdbcTemplate;
#Mock
private ResultSet resultSet;
#Captor
private ArgumentCaptor<RowMapper<Employee>> rowMapperCaptor;
#Before
public void prepareTest() throws SQLException {
when(resultSet.getLong("CUSTOMER_ID")).thenReturn(CUSTOMER_ID);
when(resultSet.getLong("ANCHOR_CUSTOMER_ID")).thenReturn(ANCHOR_CUSTOMER_ID);
when(resultSet.getString("SEGMENT")).thenReturn(SEGMENT);
when(resultSet.getDate("END_TS")).thenReturn(END_TS);
}
#Test
public void test() throws SQLException {
dao.findByCustIdAndType(0, null);
verify(jdbcTemplate).query(anyString(), rowMapperCaptor.capture());
RowMapper<Employee> rowMapper = rowMapperCaptor.getValue();
Employee employee = rowMapper.mapRow(resultSet, 1);
assertEquals(CUSTOMER_ID, employee.getCustomerId());
assertEquals(ANCHOR_CUSTOMER_ID, employee.getAnchorCustomerId());
assertEquals(SEGMENT, employee.getSegment());
assertEquals(END_TS, employee.getEndTs());
}
}
I am writing an integration test for elasticsearch 5.3.
public class ProtectedWordsIndexTests extends ESIntegTestCase {
private final WordDelimiterActionListener wordsListener =
WordDelimiterActionListener.getInstance();
private final static String INDEX_NAME = "protected_words";
private final static String TYPE_NAME = "word";
private final static String FILTER_NAME = "my_word_delimiter";
#Override
protected Collection<Class<? extends Plugin>> nodePlugins() {
return Collections.singleton(WordDelimiterPlugin.class);
}
#Override
protected Settings nodeSettings(int nodeOrdinal) {
return builder()
.put("plugin.types", TYPE_NAME)
.put("plugin.dynamic_word_delimiter.refresh_interval", "500ms")
.put(super.nodeSettings(nodeOrdinal))
.build();
}
public void testAddWordToIndex() throws Exception {
Settings indexSettings = builder()
.put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT)
.put("index.analysis.filter.my_word_delimiter.type", "dynamic_word_delimiter")
.build();
TokenFilterFactory filterFactory = filterFactory(indexSettings, FILTER_NAME);
createIndex(INDEX_NAME);
ensureGreen();
client().prepareIndex(INDEX_NAME, TYPE_NAME, "1")
.setSource("word", "1tb")
.execute();
Thread.sleep(TimeValue.timeValueSeconds(1).getMillis());
Set<String> protectedWords = wordsListener.getProtectedWords();
assertTrue(protectedWords.size() == 1);
}
}
When I am running testAddWordToIndex() I am getting the following error:
"java.lang.IllegalArgumentException: unknown setting
[plugin.dynamic_word_delimiter.refresh_interval] please check that any
required plugins are installed, or check the breaking changes
documentation for removed settings"
If I remove the following part and increase the refresh interval to be more than the default, the test passes. So I just can't override this.
.put("plugin.dynamic_word_delimiter.refresh_interval", "500ms")
The default refresh interval is declared here:
public class WordDelimiterRunnable extends AbstractRunnable {
public static final TimeValue REFRESH_INTERVAL = TimeValue.timeValueSeconds(20);
public static final String INDEX_NAME = "protected_words";
public static final String INDEX_TYPE = "word";
public static final int RESULTS_SIZE = 10000;
private volatile boolean running;
private final Client client;
private final String index;
private final long interval;
private final String type;
public WordDelimiterRunnable(Client client, Settings settings) {
this.client = client;
this.index = settings.get("plugin.dynamic_word_delimiter.protected_words_index", INDEX_NAME);
this.type = settings.get("plugin.dynamic_word_delimiter.protected_words_type", INDEX_TYPE);
this.interval = settings.getAsTime("plugin.dynamic_word_delimiter.refresh_interval", REFRESH_INTERVAL).getMillis();
}
// more code here
}
You need to register the setting using the SettingsModule#registerSettings(Setting) method as explain here:
https://www.elastic.co/guide/en/elasticsearch/reference/5.x/breaking_50_settings_changes.html#breaking_50_settings_changes
package pl.mielecmichal.news.services.news;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import pl.mielecmichal.news.entities.news.News;
import pl.mielecmichal.news.entities.newssources.NewsSource;
import pl.mielecmichal.news.repositories.news.NewsRepository;
import static java.util.Arrays.asList;
import static org.mockito.Mockito.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class NewsServiceTest {
NewsService newsService;
NewsRepository newsRepository;
private static final String FIRST_AUTHOR = "first#mail.com";
private static final String FIRST_TITLE = "First Title";
private static final String FIRST_CONTENT = "First Content Content Content";
private static final String FIRST_URL = "http://localhost/first";
private static final String SECOND_AUTHOR = "second#mail.com";
private static final String SECOND_TITLE = "Second";
private static final String SECOND_CONTENT = "Second Content";
private static final String THIRD_AUTHOR = "third#mail.com";
private static final String THIRD_TITLE = "Third Title";
private static final String THIRD_CONTENT = "Third Content";
private final News firstNews = firstCorrectNews();
private final News secondNews = secondCorrectNews();
private final News thirdNews = thirdCorrectNews();
private final NewsSource source = correctSource();
public NewsServiceTest() throws MalformedURLException {
}
#Before
public void setUp() throws MalformedURLException {
newsRepository = mock(NewsRepository.class);
newsService = new NewsService(newsRepository);
}
#Test
public void saveNewNewses_savedNewsesGivenAgain_shouldSaveOnlyNew() {
// given
List<News> newses = new ArrayList<>();
newses.add(firstNews);
newses.add(secondNews);
when(newsRepository.countByNewsSourceAndAuthorAndTitle(source, FIRST_AUTHOR, FIRST_TITLE)).thenReturn(0L);
when(newsRepository.countByNewsSourceAndAuthorAndTitle(source, SECOND_AUTHOR, SECOND_TITLE)).thenReturn(1L);
// when
newsService.saveNewNewses(newses);
// then
verify(newsRepository, times(1)).save(asList(firstNews));
verify(newsRepository, never()).save(newses);
}
private News firstCorrectNews() {
News news = new News();
news.setAuthor(FIRST_AUTHOR);
news.setTitle(FIRST_TITLE);
news.setContent(FIRST_CONTENT);
news.setNewsSource(source);
return news;
}
private News secondCorrectNews() {
News news = new News();
news.setAuthor(SECOND_AUTHOR);
news.setTitle(SECOND_TITLE);
news.setContent(SECOND_CONTENT);
news.setNewsSource(source);
return news;
}
private News thirdCorrectNews() {
News news = new News();
news.setAuthor(THIRD_AUTHOR);
news.setTitle(THIRD_TITLE);
news.setContent(THIRD_CONTENT);
news.setNewsSource(source);
return news;
}
private NewsSource correctSource() throws MalformedURLException {
NewsSource source = new NewsSource();
source.setUrl(new URL(FIRST_URL));
source.setUpdateTime(LocalDateTime.now());
return source;
}
}
I checked under debugger, and countBy method always returns O, but arguments are different and correct in my SUT. It looks like Mockito doesn't
distinguish a method arguments. Cheers!
I add full source code to show that constants are correct.
While the issue is chiefly the ordering of your fields, there are some things you can do to both reduce the likelihood of this error occurring again, and clean up your test quite a bit.
First and foremost, your three methods - firstCorrectNews, secondCorrectNews, and thirdCorrectNews - all do the exact same thing with slightly different parameters. It makes more sense to unify their purpose.
private News correctNews(final String author, final String title, final String content, final NewsSource source) {
final News news = new News();
news.setAuthor(author);
news.setTitle(title);
news.setContent(content);
news.setNewsSource(source);
return news;
}
If you use this method to bootstrap your test news objects, you are forced to pass in a source every time, so that you won't get caught up in anything that's dependent on the state of your test object overall.
While we're here, it's worth modifying the correctSource method too, so that we pass in the URL instead of assuming state once more.
private NewsSource correctSource(final String url) throws MalformedURLException {
final NewsSource source = new NewsSource();
source.setUrl(new URL(url));
source.setUpdateTime(LocalDateTime.now());
return source;
}
Next, we can leverage Mockito's runner class so that we don't have to new up the mocks in a #Before clause. This keeps the code a lot smaller and makes the expectations of what the mocked classes and the test class are.
#RunWith(MockitoJUnitRunner.class)
public class NewsServiceTest {
#Mock
NewsRepository newsRepository;
#InjectMocks
NewsService newsService;
// other code to follow
}
Now, let's put it all together. This should be the exact same thing that you're testing, with the main differences being:
There's a lot less repetition in your code, specifically when bootstrapping expected data
Your mocks are clearly defined
All test data that you genuinely care about is in your specific test, which helps when you want to write more tests
Your test data is also isolated in scope, which allows you to run this test in parallel
#RunWith(MockitoJUnitRunner.class)
public class NewsServiceTest {
#Mock
NewsRepository newsRepository;
#InjectMocks
NewsService newsService;
#Test
public void saveNewNewses_savedNewsesGivenAgain_shouldSaveOnlyNew() {
// given
final String FIRST_AUTHOR = "first#mail.com";
final String FIRST_TITLE = "First Title";
final String FIRST_CONTENT = "First Content Content Content";
final String URL = "http://localhost/first";
final String SECOND_AUTHOR = "second#mail.com";
final String SECOND_TITLE = "Second";
final String SECOND_CONTENT = "Second Content";
final List<News> newses = new ArrayList<>();
final NewsSource newsSource = correctSource(URL);
final News firstNews = correctNews(FIRST_AUTHOR, FIRST_TITLE, FIRST_CONTENT, newsSource);
final News secondNews = correctNews(SECOND_AUTHOR, SECOND_TITLE, SECOND_CONTENT, newsSource);
newses.add(firstNews);
newses.add(secondNews);
// when
when(newsRepository.countByNewsSourceAndAuthorAndTitle(newsSource, FIRST_AUTHOR, FIRST_TITLE)).thenReturn(0L);
when(newsRepository.countByNewsSourceAndAuthorAndTitle(newsSource, SECOND_AUTHOR, SECOND_TITLE)).thenReturn(1L);
newsService.saveNewNewses(newses);
// then
verify(newsRepository).save(asList(firstNews));
verify(newsRepository, never()).save(newses);
}
private News correctNews(final String author, final String title, final String content, final NewsSource source) {
final News news = new News();
news.setAuthor(author);
news.setTitle(title);
news.setContent(content);
news.setNewsSource(source);
return news;
}
private NewsSource correctSource(final String url) throws MalformedURLException {
NewsSource source = new NewsSource();
source.setUrl(new URL(url));
source.setUpdateTime(LocalDateTime.now());
return source;
}
}
Ok, the problem is an order of initialization newses and source.
When I create firstNews, secondNews, thirdNews, the source object is null yet. But inside my test it is fully initialized.
private final NewsSource source = correctSource(); //should be here
private final News firstNews = firstCorrectNews();
private final News secondNews = secondCorrectNews();
private final News thirdNews = thirdCorrectNews();
private final NewsSource source = correctSource(); //not here
I have to initialize some final variable,but these value need to be read by Spring Properties
public class CrawlerClient{
#Autowired
#Qualifier("crawlerProperties")
private Properties crawlerProperties;
private Integer final maxTopic;
public static void main(String[] args) {
//initialize();
}
#PostConstruct
private void initialize(){
List<Topic> topics = topicBusiness.getAll();
List<Blogger> bloggers = bloggerBusiness.getAll();
List<Clue> clues = clueBusiness.getAll();
ClueQueue.addAll(clues);
TopicQueue.addAll(topics);
BloggerQueue.addAll(bloggers);
}
..
}
I want to initialize the variable of "maxTopic",but value is in Properties,So I can't do it in construction,how can I do for this?I just know remove the key of "final".
Finally,I do it by this way:
final Integer maxTopic;
final Integer maxBlogger;
final Integer maxClue;
#Autowired
public CrawlerClient(#Qualifier("crawlerProperties")Properties crawlerProperties){
this.maxTopic = Integer.parseInt(crawlerProperties.getProperty("MaxTopic"));
this.maxBlogger = Integer.parseInt(crawlerProperties.getProperty("MaxBlogger"));
this.maxClue = Integer.parseInt(crawlerProperties.getProperty("MaxClue"));
}
Can anyone solve it by the better way?
I believe you can achieve what you want with constructor injection:
#Component
public class CrawlerClient{
private Properties crawlerProperties;
private final Integer maxTopic;
#Autowired
public CrawlerClient(#Qualifier("crawlerProperties") Properties crawlerProperties,
#Value("maxTopic") Integer maxTopic){
this.crawlerProperties = crawlerProperties;
this.maxTopic = maxTopic;
List<Topic> topics = topicBusiness.getAll();
List<Blogger> bloggers = bloggerBusiness.getAll();
List<Clue> clues = clueBusiness.getAll();
ClueQueue.addAll(clues);
TopicQueue.addAll(topics);
BloggerQueue.addAll(bloggers);
}
..
}