I am using mockito-junit to test a piece of my code. As progressing I found out that there was an interface implemented in the main file which I was testing, when the test was running I found that the line where interface method is called get's covered but the real method doesn't get's covered.
This the code for the main file:
public class ExtractCurrencyDataTask {
private static final Logger LOGGER = LoggerFactory.getLogger(ExtractCurrencyDataTask.class);
#Autowired
private ExtractCurrencyService extractCurrencyService;
public void writeCurrencyListToFile(List<Currency> currencyList) {
if (currencyList != null && !currencyList.isEmpty()) {
String dir = "A path";
String fileName = "A filename";
String writeToFile = dir + "/" + fileName + ".writing";
String renameFile = dir + "/" + fileName + ".dat";
BufferedWriter writer = null;
FileWriter fileWriter = null;
try {
fileWriter = new FileWriter(writeToFile);
writer = new BufferedWriter(fileWriter);
extractCurrencyService.extractCurrencyList(currencyList, writer);
} catch (Exception e) {
throw new RuntimeException("Error writing Currency codes", e);
} finally {
if (writer != null) {
try {
writer.close();
fileWriter.close();
} catch (IOException e) {
LOGGER.info("Exception occured while closing the file writer", e);
}
moveFile(writeToFile, renameFile);
}
}
}
}
private void moveFile(String writeToFile, String renameFile) {
try {
FileUtils.moveFile(FileUtils.getFile(writeToFile), FileUtils.getFile(renameFile));
} catch (IOException e) {
LOGGER.info("Exception occured while moving file from writing to dat", e);
}
}
Here extractCurrencyService is the interface which I have mentioned.
The interface:
public interface ExtractCurrencyService {
public void extractCurrencyList(List<Currency> currency, Writer writer);
}
This the method definition which is done another file which implements same interface Filename:ExtractCurrencyServiceImpl.java
public class ExtractCurrencyServiceImpl implements ExtractCurrencyService {
private static final String SEP = "|";
private static final String NEWLINE = "\n";
#Override
public void extractCurrencyList(List<Currency> currencyList, Writer writer) {
if (currencyList != null) {
currencyList.forEach(currency -> {
String code = currency.getCode();
String name = currency.getName() == null ? "" : currency.getName();
Long noOfDecimals = currency.getNumberOfDecimals();
RoundingMethodValue roundingMethod = currency.getRoundingMethod();
boolean isDealCurrency = currency.isDealCurrency();
String description = currency.getDescription() == null ? "" : currency.getDescription();
try {
writer.write(createCurrencyDataLine(code,
name,
noOfDecimals,
roundingMethod,
isDealCurrency,
description));
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
}
private String createCurrencyDataLine(String code,
String name,
Long noOfDecimals,
RoundingMethodValue roundingMethod,
boolean isdealCurrency,
String description) {
return code + SEP + name + SEP + noOfDecimals.toString() + SEP + roundingMethod.toString() + SEP
+ isdealCurrency + SEP + description + NEWLINE;
}
public static <T> Predicate<T> distinctByKey(Function<? super T, Object> keyExtractor) {
Map<Object, Boolean> map = new ConcurrentHashMap<>();
return t -> map.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}
}
This is the test file:
#RunWith(MockitoJUnitRunner.class)
public class ExtractCurrencyDataTaskTest {
#Mock
private Currency mockCurrency;
#Mock
private ExtractCurrencyService mockExtractCurrencyService;
#Mock
private BufferedWriter mockBufferWriter;
#Mock
private Bean mockBean;
#InjectMocks
private ExtractCurrencyDataTask extractCurrencyDataTask;
#Test
public void writeCurrencyListToFileTest() {
List<Currency> currencyList = new ArrayList();
when(mockCurrency.getCode()).thenReturn("USD");
when(mockCurrency.getNumberOfDecimals()).thenReturn((long) 2);
when(mockCurrency.getRoundingMethod()).thenReturn(enum value);
when(mockCurrency.isDealCurrency()).thenReturn(true);
when(mockCurrency.getName()).thenReturn("US Dollars");
when(mockCurrency.getDescription()).thenReturn("Currency Description");
currencyList.add(mockCurrency);
extractCurrencyDataTask.writeCurrencyListToFile(currencyList);
}
}
This the configuration for Autowired bean
#Bean
public ExtractCurrencyService extractCurrencyService() {
return new ExtractCurrencyServiceImpl();
}
As you can see the real output of this process is a file will be created in a path mentioned with some data. Here in this test I am mocking the data and passing it to main file. Main file is the created file in respective path but there is no data in the file.
The data writing part is done by the interface method. This is the part where I need help.
Thanks in advance....
You are injecting a mock of ExtractCurrencyService into your tested class. So the test is running with this mock instance instead of ExtractCurrencyServiceImpl. The current behaviour is that your ExtractCurrencyDataTasktested class is calling to extractCurrencyService#extractCurrencyList, but this extractCurrencyService is a mock, not your real implementation, so the call is done but it does nothing.
If you want to unit test ExtractCurrencyDataTask then thats ok, but maybe you should assert the call to extractCurrencyService#extractCurrencyList is done in the way you expect.
If you want to unit test ExtractCurrencyServiceImpl then create a unit test for this class.
If you want to test the interaction between these two classes then create an integration test where ExtractCurrencyDataTask has injected a real instance of ExtractCurrencyServiceImpl, not a mock.
Related
I have a application.yaml
app:
list: /list.txt
list.txt
Also I have a file with list of strings. It locates into /resources(in the root /resource).
first
second
third
class
public class Bean{
#Value("${app.list}")
private List<String> listProp = new ArrayList<>();
public void print(){
System.out.println(listProp);
}
}
I have found that:
public class ResourceReader {
public static String asString(Resource resource) {
try (Reader reader = new InputStreamReader(resource.getInputStream(), UTF_8)) {
return FileCopyUtils.copyToString(reader);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
public static String readFileToString(String path) {
ResourceLoader resourceLoader = new DefaultResourceLoader();
Resource resource = resourceLoader.getResource(path);
return asString(resource);
}
}
#Configuration
public class ConfigurationResource {
#Configuration
public class ConfigurationResource {
#Value("${app.list}")
private String pathToFile;
#Bean
public List<String> resourceString() {
String blackList = ResourceReader.readFileToString(pathToFile);
return List.of(blackList.split("\n"));
}
}
}
#RequiredArgsConstructor
public class HelloController {
private final List<String> resourceString;
...
}
This is necessary in order not to manually write a list of strings to the property app.name (there are several hundred lines).
However, I find it difficult to figure out how to do it at low cost. So that it can be easily maintained.
maybe there is an easier way ? I would not like to add a hardcoding value in the configuration class
Maybe someone has some ideas ?
Here is the solution from my understanding if you have to keep lines in the text file that you've shared:
public class Bean {
#Value("${app.list}")
private String listProp; // only get name of file
public void print(){
ClassLoader classLoader = getClass().getClassLoader();
InputStream is = classLoader.getResourceAsStream(listProp);
StringBuilder sb = new StringBuilder();
try {
for (int ch; (ch = is.read()) != -1; ) {
sb.append((char) ch);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
System.out.println(sb);
}
}
I am trying to stub a void method of a mocked object to return an exception. This mocked object passed as dependency to the service which I am writing tests for.
Service:
#Component
public class FileHandler {
private static final Logger log = LoggerFactory.getLogger(FileHandler.class);
private final SSHAccess sshAccess;
#Value("${datastore.location}")
private String dataStoreDir;
public FileHandler(SSHAccess sshAccess){
this.sshAccess = sshAccess;
}
public Either<Pair<Exception, FileRecord>, FileRecord> transferFile(FileRecord fileRecord){
try {
var sourceURI = new URI(fileRecord.getSourceURI());
var digest = sshAccess.execute("md5sum " + sourceURI.getPath())
.replaceFirst("([^\\s]+)[\\d\\D]*", "$1");
if (digest.equals(fileRecord.getSourceDigest())) {
log.info(Thread.currentThread().getName() + ": Copying file: " + fileRecord.getId() + " of submission: " + fileRecord.getOwnedBy());
sshAccess.download(sourceURI.getPath(),new File(mkdir(dataStoreDir, digest), digest));
log.info(Thread.currentThread().getName() + ": Copying of file: " + fileRecord.getId() + " of submission: " + fileRecord.getOwnedBy() + " finished.");
return Either.Right(fileRecord);
}else{
log.error("MD5 mismatch for source file {}", sourceURI.getPath());
return Either.Left(Pair.of(new FileHandlerException("MD5 mismatch"), fileRecord));
}
} catch (URISyntaxException
| IOException
e) {
return Either.Left(Pair.of(new FileHandlerException(e), fileRecord));
}
}
private File mkdir(String dataStoreDir, String digest) throws IOException {
File dir = new File(dataStoreDir, digest.substring(0, 3));
if (!dir.exists() && !dir.mkdirs()) {
log.error("Unable to create directory {}", dir);
throw new IOException("Unable to create directory " + dir);
}
return dir;
}
}
Test Class:
#SpringBootTest
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class FileHandlerTest {
private FileHandler fileHandler;
#Mock
private SSHAccess sshAccess;
#BeforeAll
public void init(){
fileHandler = Mockito.spy(new FileHandler(sshAccess));
}
#Test
public void transferFileShouldReturnFileHandlerExceptionOnEitherLeftWhenSSHclientThrowsIOException() throws IOException {
FileRecord fileRecord = getFileRecord();
var digest = "6e484ac23110fae10021e";
when(sshAccess.execute(anyString())).thenReturn(digest);
doThrow(IOException.class).when(sshAccess).download(anyString(), any(File.class));
var result = fileHandler.transferFile(fileRecord);
Assertions.assertTrue(result.getLeft().isPresent()
&& result.getLeft().get().getFirst() instanceof FileHandlerException);
}
private FileRecord getFileRecord() {
var fileRecord = new FileRecord();
fileRecord.setId(1L);
fileRecord.setOwnedBy(1000);
fileRecord.setSourceURI("scp:host/test/uri/filename");
fileRecord.setSourceDigest("6e484ac23110fae10021e");
return fileRecord;
}
}
But when I run this test case, doThrow() doesn't throw any exception. Method executed without any exception and test failed. I am not sure what I am doing wrong here. Please help.
Not sure why are you using the #SpringBootTest annotation which will try to raise a similar context with the one when you are running the app. So in this case you could stop instantiating your FileHandler and just spy on it and on your SSHAccess beans or use #MockBean instead of #Mock.
Basically you should have something like this
#SpyBean
private FileHandler fileHandler;
#MockBean
private SSHAccess sshAccess;
You are using Junit 5. For Junit 5 you don't need the method #SpringBootTest, you need to use #ExtendWith(MockitoExtension.class)
#SpringBootTest
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class FileHandlerTest {
private FileHandler fileHandler;
#Mock
private SSHAccess sshAccess;
#BeforeAll
public void init(){
fileHandler = Mockito.spy(new FileHandler(sshAccess));
}
.....
.....
.....
}
Also instead of Mockito.spy(new FileHandler(sshAccess)) you can try Mockito.mock(new FileHandler(sshAccess))
I have a class A as
Class A{
private static final String ANON_DIR = "/webapps/worldlingo/data/anonymizer/";
private static final String NO_ANON = "noanonymize";
public String first(String text, String srclang, Map dictTokens) {
Set<String> noAnonymize = new HashSet<String>();
second(noAnonymize,ANON_DIR + NO_ANON, "tmpLang","name");
String value;
if(noAnonymize.contains("test")){
value = "test1";
}
else {
value = "test";
}
return value;
}
where ANON_DIR and NO_ANON is static final value. This class has function first and function second .The first function has a calling method in it which calls second function. The second function is void function which takes static fields as parameter.
Second function is just the file read function with the path provided as
public void second (Set<String> hashSet, String path, String lang , String type) {
FileReader fr = null;
BufferedReader br = null;
try {
fr = new FileReader(path);
br = new BufferedReader(fr);
String Line;
while ((Line = br.readLine()) != null) {
hashSet.add(Line);
}
} catch (IOException e) {
log.error("Anonymizer: Unable to load file.", e);
} finally {
try {
if (fr != null) {
fr.close();
}
if (br != null) {
br.close();
}
} catch (IOException e) {
log.error("Anonymizer : An error occured while closing a resource.", e);
}
}
}
}
Now I am trying to test the function first using mockito. I am trying update the passed first argument (list parameter) i.e noAnonymize in second(noAnonymize,ANON_DIR + NO_ANON, "tmpLang","name");
public void testfirst() throws Exception {
Anonymizer mock = PowerMockito.mock(Anonymizer.class);
doAnswer(new Answer() {
public Object answer(InvocationOnMock invocation) {
List<String> args = invocation.getArgumentAt(0,List.class);
args.add("a");
args.add("b");
return null; // void method, so return null
}
}).when(mock).readNoAnonymizeFile(Mockito.anySet(),Mockito.anyString(),Mockito.anyString(),Mockito.anyString());
Method anonymizeNames = anon.getClass().getDeclaredMethod("anonymizeNames_test", String.class, String.class, Map.class);
String srcLang = "MSFT_EN";
Map mapTokens = new HashMap();
String result = (String) anonymizeNames.invoke(anon,"I am David",srcLang,mapTokens);
}
PROBLEM:
I am not able to mock the void second method to update list with value a and b. How can I have the mockto test case to update parameter in void method.
When unit testing a class, you test it through its public methods. If you can't test the class sufficiently through its public methods, it needs re-factored.
In this case, you're trying to unit test a private method for an edge case that doesn't exist. Why even provide the constant as a parameter? Why not reference it directly in the private method and save passing an argument? Instead, you could write:
fr = new FileReader(ANON_DIR + NO_ANON);
EDIT
After Laxmi and I had a discussion we came up with a solution using constructor based dependency injection and changing the void method to return Set<String>. This let us test in isolation and mock easily.
I hava a class Engine.class
with static function
public static HashMap<String, String> loadLanguageCodeFile(HashMap<String,String> hash_map) {
SystemSettings settings;
FileReader fr = null;
BufferedReader br = null;
try {
settings = SystemSettings.GetInstance();
String path = settings.getLangCodePath();
fr = new FileReader(path + FILENAME);
br = new BufferedReader(fr);
String Line;
while ((Line = br.readLine())!= null) {
String[] lang_codes = Line.split("\\s+");
hash_map.put(lang_codes[0], lang_codes[1]);
}
} catch (IOException e) {
log.error("MicrosoftEngine: Unable to load file.", e);
} catch (WorldlingoException e){
log.error("MicrosoftEngine:", e);
}
finally {
try {
if (fr != null) {
fr.close();
}
if (br != null) {
br.close();
}
} catch ( IOException e) {
log.error("MicrosoftEngine : An error occured while closing a resource.", e);
}
}
return hash_map;
}
I am trying to write a test case for this method. Systemsetting is another class and
settings = SystemSettings.GetInstance();
String path = settings.getLangCodePath();
` gives the instance of another class and contains path file like \var\log file in path .
I am trying to write a test case using mockito. Since it is an static class, I used powermockito.
#RunWith(PowerMockRunner.class)
#PrepareForTest({HttpClientBuilder.class,Engine.class, SystemSettings.class})
public class EngineTest extends TestCase {
public void testLoadLanguageCodeFile() throws Exception {
PowerMockito.mockStatic(Engine.class);
PowerMockito.mockStatic(SystemSettings.class);
MicrosoftEngine MSmock = Mockito.mock(Engine.class);
SystemSettings SystemSettingsMock = Mockito.mock(SystemSettings.class);
Mockito.when(SystemSettingsMock.GetInstance()).thenReturn(SystemSettingsMock);
HashMap<String, String> hash_map = new HashMap<String, String>();
MSmock.loadLanguageCodeFile(hash_map);
}
I am not able to call the above loadLanguageCodeFile method. Any suggestion how to call static method will be appreciated
You are not suppose to mock the subject under test. You mock the dependencies of the subject under test that are needed to exercise the test to completion.
The code is also tightly coupled to implementation concerns like the file reader and buffer reader.
However as indicated in the comments you want to test the actual reading of a file at the path provided by the mocked settings.
In that case you only need to mock SystemSettings and should call the actual member under test
RunWith(PowerMockRunner.class)
#PrepareForTest({SystemSettings.class})
public class EngineTest extends TestCase {
public void testLoadLanguageCodeFile() throws Exception {
//Arrange
String path = "Path to test file to be read";
PowerMockito.mockStatic(SystemSettings.class);
//instance mock
SystemSettings settings = Mockito.mock(SystemSettings.class);
Mockito.when(settings.getLangCodePath()).thenReturn(path);
//mock static call
Mockito.when(SystemSettings.GetInstance()).thenReturn(settings);
HashMap<String, String> hash_map = new HashMap<String, String>();
//Act
HashMap<String, String> actual = Engine.loadLanguageCodeFile(hash_map);
//Assert
//perform assertion
}
}
Reference Using PowerMock with Mockito: Mocking Static Metho
I want to test MessageProcessor1.listAllKeyword method, which in turn
calls HbaseUtil1.getAllKeyword method. Initialy, I had to deal with a problem associated with the static initializer and the constructor. The problem was to initialize a HBASE DB connection. I used powerMock to suppress static and constructor calls and it worked fine.
Even though I mocked HbaseUtil1.getAllKeyword method, actual method is being called and executes all HBase code leading to an exception, in which HBASE server is not up.
EasyMock.expect(hbaseUtil.getAllKeyword("msg", "u1")).andReturn(expectedList);
Please give me any idea on how to avoid an actual method call. I tried many ways but none of them worked.
public class MessageProcessor1
{
private static Logger logger = Logger.getLogger("MQ-Processor");
private final static String CLASS_NAME = "MessageProcessor";
private static boolean keywordsTableExists = false;
public static PropertiesLoader props;
HbaseUtil1 hbaseUtil;
/**
* For checking if table exists in HBase. If doesn't exists, will create a
* new table. This runs only once when class is loaded.
*/
static {
props = new PropertiesLoader();
String[] userTablefamilys = {
props.getProperty(Constants.COLUMN_FAMILY_NAME_COMMON_KEYWORDS),
props.getProperty(Constants.COLUMN_FAMILY_NAME_USER_KEYWORDS) };
keywordsTableExists = new HbaseUtil()
.creatTable(props.getProperty(Constants.HBASE_TABLE_NAME),
userTablefamilys);
}
/**
* This will load new configuration every time this class instantiated.
*/
{
props = new PropertiesLoader();
}
public String listAllKeyword(String userId) throws IOException {
HbaseUtil1 util = new HbaseUtil1();
Map<String, List<String>> projKeyMap = new HashMap<String, List<String>>();
//logger.info(CLASS_NAME+": inside listAllKeyword method");
//logger.debug("passed id : "+userId);
List<String> qualifiers = util.getAllKeyword("msg", userId);
List<String> keywords = null;
for (String qualifier : qualifiers) {
String[] token = qualifier.split(":");
if (projKeyMap.containsKey(token[0])) {
projKeyMap.get(token[0]).add(token[1]);
} else {
keywords = new ArrayList<String>();
keywords.add(token[1]);
projKeyMap.put(token[0], keywords);
}
}
List<Project> projects = buildProject(projKeyMap);
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation()
.create();
System.out.println("Json projects:::" + gson.toJson(projects));
//logger.debug("list all keyword based on project::::"+ gson.toJson(projects));
//return gson.toJson(projects);
return "raj";
}
private List<Project> buildProject(Map<String, List<String>> projKeyMap) {
List<Project> projects = new ArrayList<Project>();
Project proj = null;
Set<String> keySet = projKeyMap.keySet();
for (String hKey : keySet) {
proj = new Project(hKey, projKeyMap.get(hKey));
projects.add(proj);
}
return projects;
}
//#Autowired
//#Qualifier("hbaseUtil1")
public void setHbaseUtil(HbaseUtil1 hbaseUtil) {
this.hbaseUtil = hbaseUtil;
}
}
public class HbaseUtil1 {
private static Logger logger = Logger.getLogger("MQ-Processor");
private final static String CLASS_NAME = "HbaseUtil";
private static Configuration conf = null;
public HbaseUtil1() {
PropertiesLoader props = new PropertiesLoader();
conf = HBaseConfiguration.create();
conf.set(HConstants.ZOOKEEPER_QUORUM, props
.getProperty(Constants.HBASE_CONFIGURATION_ZOOKEEPER_QUORUM));
conf.set(
HConstants.ZOOKEEPER_CLIENT_PORT,
props.getProperty(Constants.HBASE_CONFIGURATION_ZOOKEEPER_CLIENT_PORT));
conf.set("hbase.zookeeper.quorum", props
.getProperty(Constants.HBASE_CONFIGURATION_ZOOKEEPER_QUORUM));
conf.set(
"hbase.zookeeper.property.clientPort",
props.getProperty(Constants.HBASE_CONFIGURATION_ZOOKEEPER_CLIENT_PORT));
}
public List<String> getAllKeyword(String tableName, String rowKey)
throws IOException {
List<String> qualifiers = new ArrayList<String>();
HTable table = new HTable(conf, tableName);
Get get = new Get(rowKey.getBytes());
Result rs = table.get(get);
for (KeyValue kv : rs.raw()) {
System.out.println("KV: " + kv + ", keyword: "
+ Bytes.toString(kv.getRow()) + ", quaifier: "
+ Bytes.toString(kv.getQualifier()) + ", family: "
+ Bytes.toString(kv.getFamily()) + ", value: "
+ Bytes.toString(kv.getValue()));
qualifiers.add(new String(kv.getQualifier()));
}
table.close();
return qualifiers;
}
/**
* Create a table
*
* #param tableName
* name of table to be created.
* #param familys
* Array of the name of column families to be created with table
* #throws IOException
*/
public boolean creatTable(String tableName, String[] familys) {
HBaseAdmin admin = null;
boolean tableCreated = false;
try {
admin = new HBaseAdmin(conf);
if (!admin.tableExists(tableName)) {
HTableDescriptor tableDesc = new HTableDescriptor(tableName);
for (int i = 0; i < familys.length; i++) {
tableDesc.addFamily(new HColumnDescriptor(familys[i]));
}
admin.createTable(tableDesc);
System.out.println("create table " + tableName + " ok.");
}
tableCreated = true;
admin.close();
} catch (MasterNotRunningException e1) {
e1.printStackTrace();
} catch (ZooKeeperConnectionException e1) {
e1.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return tableCreated;
}
}
Below is my Test class.
#RunWith(PowerMockRunner.class)
#PrepareForTest(MessageProcessor1.class)
#SuppressStaticInitializationFor("com.serendio.msg.mqProcessor.MessageProcessor1")
public class MessageProcessorTest1 {
private MessageProcessor1 messageProcessor;
private HbaseUtil1 hbaseUtil;
#Before
public void setUp() {
messageProcessor = new MessageProcessor1();
hbaseUtil = EasyMock.createMock(HbaseUtil1.class);
}
#Test
public void testListAllKeyword(){
List<String> expectedList = new ArrayList<String>();
expectedList.add("raj:abc");
suppress(constructor(HbaseUtil1.class));
//suppress(method(HbaseUtil1.class, "getAllKeyword"));
try {
EasyMock.expect(hbaseUtil.getAllKeyword("msg", "u1")).andReturn(expectedList);
EasyMock.replay();
assertEquals("raj", messageProcessor.listAllKeyword("u1"));
} catch (IOException e) {
e.printStackTrace();
}catch (Exception e) {
e.printStackTrace();
}
}
}
The HbaseUtil1 is instantiated within the listAllKeyword method
public String listAllKeyword(String userId) throws IOException {
HbaseUtil1 util = new HbaseUtil1();
...
So the mock one you create in your test isn't being used at all.
If possible, make the HbaseUtil1 object passable, or settable on the MessageProcessor1 class and then set it in the test class.
Also, and note I'm not 100% familiar with PowerMock, you could include HbaseUtil1 in the prepare for test annotation. I think that will make PowerMock instantiate mocks instead of real objects and then use the expectations you provide in you test.