Mocking DirectoryStream<Path> without mocking iterator possible? - java

I've the following piece of code:
Map<String, String> fileContentsByName = new HashMap<String, String>();
try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(directory))
{
for (Path path : directoryStream)
{
if (Files.isRegularFile(path))
{
fileContentsByName.put(path.getFileName().toString(), new String(Files.readAllBytes(path)));
}
}
}
catch (IOException e)
{
}
I am attempting to test this method. I'm using Powermock to get the mocked DirectoryStream<Path>. However, when the test encounters for-each in the code, it blows up with a NPE. How can I specify the Paths in the DirectoryStream?
I've thought about changing the source code to use iterator and mocking the DirectoryStream's iterator to provide the desired paths but I am wondering if there a better alternative?

Assuming that the code snippet you provided above is defined in a class like so:
public class DirectoryStreamReader {
public Map<String, String> read(Path directory) {
Map<String, String> fileContentsByName = new HashMap<String, String>();
try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(directory)) {
for (Path path : directoryStream) {
if (Files.isRegularFile(path)) {
fileContentsByName.put(path.getFileName().toString(), new String(Files.readAllBytes(path)));
}
}
} catch (IOException e) {
}
return fileContentsByName;
}
}
Then the following test will pass:
#RunWith(PowerMockRunner.class)
#PrepareForTest({DirectoryStreamReader.class})
public class DirectoryStreamTest {
#Rule
public TemporaryFolder folder= new TemporaryFolder();
#Test
public void canReadFilesUsingDirectoryStream() throws IOException {
PowerMockito.mockStatic(Files.class);
Path directory = Mockito.mock(Path.class);
DirectoryStream<Path> expected = Mockito.mock(DirectoryStream.class);
Mockito.when(Files.newDirectoryStream(Mockito.any(Path.class))).thenReturn(expected);
File fileOne = folder.newFile();
File fileTwo = folder.newFile();
Iterator<Path> directoryIterator = Lists.newArrayList(Paths.get(fileOne.toURI()),
Paths.get(fileTwo.toURI())).iterator();
Mockito.when(expected.iterator()).thenReturn(directoryIterator);
Mockito.when(Files.isRegularFile(Mockito.any(Path.class))).thenReturn(true);
Mockito.when(Files.readAllBytes(Mockito.any(Path.class))).thenReturn("fileOneContents".getBytes()).thenReturn("fileTwoContents".getBytes());
Map<String, String> fileContentsByName = new DirectoryStreamReader().read(directory);
Assert.assertEquals(2, fileContentsByName.size());
Assert.assertTrue(fileContentsByName.containsKey(fileOne.getName()));
Assert.assertEquals("fileOneContents", fileContentsByName.get(fileOne.getName()));
Assert.assertTrue(fileContentsByName.containsKey(fileTwo.getName()));
Assert.assertEquals("fileTwoContents", fileContentsByName.get(fileTwo.getName()));
}
}
The key points here are:
Uses JUnit's TemporaryFolder rule to create and discard some files for use by the test
Uses PowerMockito to mock all interactions with java.nio.file.Files, this is a final class and the methods being mocked are static hence the need for PowerMockito
Follows the PowerMockito advice when mocking a system class, specifically:
Use #RunWith(PowerMockRunner.class) annotation at the class-level of the test case.
Use the #PrepareForTest({ClassThatCallsTheSystemClass.class}) annotation at the class-level of the test case.
Use mockStatic(SystemClass.class) to mock the system class
This test is verified with Junit 4.12, Mockito 2.7.19 and PowerMock 1.7.0

Related

How to avoid problems with static method in #BeforeClass

I have a JUnit test class, in which I create a TemporaryFolder and test my program in this directory with newly created files and folders. Currently, everything is set up at #Before event:
#Rule
public TemporaryFolder folder = new TemporaryFolder();
File testFile1;
File testFile2;
public List<String> folderItems = new ArrayList<>();
#Before
public void initalise() throws IOException {
testFile1 = folder.newFile("test1.txt");
testFile2 = folder.newFile("test2.txt");
folderItems.add(testFile1.getName());
folderItems.add(testFile2.getName());
Directory.setCurrentDirectory(folder.getRoot().getAbsolutePath());
}
However, I read that #Before is executed before each test and since my tests will all be using the same folder structure, it is better to use #BeforeClass which is only executed once. Here, however, I have an issue with the fact that the method needs to be static. That yields error because then all the variables would need to be static. However, the list can't be static as I am adding the testFiles name into it during initialization.
Any idea how to solve this?
EDIT
I have changed the file into this:
#ClassRule
public static TemporaryFolder folder = new TemporaryFolder();
public static File testFile1;
public static File testFile2;
public static List<String> folderItems;
#BeforeClass
public static void setUpFolder() {
try {
testFile1 = folder.newFile("test1.txt");
} catch (IOException e) {
return;
}
try {
testFile2 = folder.newFile("test2.txt");
} catch (IOException e) {
return;
}
folderItems.add(testFile1.getName());
folderItems.add(testFile2.getName());
Directory.setCurrentDirectory(folder.getRoot().getAbsolutePath());
}
Despite this not throwing any errors, my test doesn't actually get executed and passed, I receive and initializationError

Condition fails in Junit5 & Mockito

I am trying to write the test case for my application and I cannot get past a condition even after providing what is expected, from what I know.
Here is my test class.
#ExtendWith(MockitoExtension.class)
class AppConfigTest {
#Mock
#TempDir
File mockedFile;
#InjectMocks
private AppConfig appConfig;
#Test
void getData() throws Exception {
File f = new File("f");
File[] files = {f};
lenient().when(mockedFile.listFiles()).thenReturn(files);
lenient().when(mockedFile.isFile()).thenReturn(true);
assertNotNull(appConfig.getData());
}
}
My implementation. The test doesn't go past the if condition. The test does not cover the code after the condition as it turns true all the time. I need my test to cover keyMap() in the last line.
private Map<String, String> getData() {
File[] files = new File(APP_CONST.DIRECTORY).listFiles();
if (null == files) { // not turning FALSE even after providing mocked "files" array
return Collections.emptyMap();
}
List<String> keysList = getKeyList(files);
return keyMap(APP_CONST.DIRECTORY, keysList);
}
Can anyone please tell me how to correct this please? Using SpringBoot/JUnit 5
We discussed this in the comments, but in any case, I guess an example is better.
One way you could go about this is to make sure the same folder exists. In the test setup you could simply create it.
#Before
public void setUp() {
new File(APP_CONST.DIRECTORY).mkdirs();
}
Now when accessing it in the implementation there will be a directory. You can also inside the test add files to the directory, so it's not empty.
Although this works, it has some issues with setting it up and cleaning it up. A better way is to abstract this from the implementation itself and use some kind of provider for it.
A suggestion would be to create an interface where the real implementation returns the real folder and in tests you can mock this.
public interface DirectoryProvider {
public File someDirectory();
}
public class RealDirectoryProvider implements DirectoryProvider {
#Override
public File someDirectory() {
return new File(APP_CONST.DIRECTORY);
}
}
you can now make the getData class depend on this abstraction. You didn't give us the class name, so don't pay attention to that part:
public class Data {
private final DirectoryProvider directoryProvider;
public Data(DirectoryProvider directoryProvider) {
this.directoryProvider = directoryProvider;
}
private Map<String, String> getData() {
File[] files = directoryProvider.someDirectory().listFiles();
if (null == files) {
return Collections.emptyMap();
}
List<String> keysList = getKeyList(files);
return keyMap(APP_CONST.DIRECTORY, keysList);
}
}
Now during the test you can just inject your mocked directory/temp dir.
#ExtendWith(MockitoExtension.class)
class AppConfigTest {
#TempDir
File mockedFile;
#Mock
DirectoryProvider directoryProvider;
#InjectMocks
private AppConfig appConfig;
#Test
void getData() throws Exception {
lenient().when(directoryProvider.someDirectory()).thenReturn(mockedFile);
assertNotNull(appConfig.getData());
}
}
You can also add files to the temp dir if you need. This however should be enough to pass the if I think.

Junit test class for dependency with value from application.properties

I am writing Junit test case for the following class :
#Component
public class ExpandParam {
/* expand parameter with value "expand" */
#Value("${api.expand.value}")
private String expandParam;
public MultiValueMap<String, String> getExpandQueryParam(String[] expand) {
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
// Creating comma separated format string
StringBuilder builder = new StringBuilder();
for (String value : expand) {
if(!expand[expand.length-1].equals(value)) {
builder.append(value+", ");
}
else {
builder.append(value);
}
}
String expandText = builder.toString();
queryParams.add(expandParam, expandText);
return queryParams;
}
}
The test class is following :
public class ExpandParamTest {
#InjectMocks
#Spy
ExpandParam expandQueryParam;
// #Value("${api.expand.value}")
// private String expandParam;
private String[] expand = {"fees"};
#Before
public void setup() {
ReflectionTestUtils.setField(expandQueryParam, "expandParam", "expand");
}
#Test
public void testExpandParam() {
MultiValueMap<String, String> queryParams = expandQueryParam.getExpandQueryParam(expand);
try {
System.out.println(new ObjectMapper().writeValueAsString(queryParams));
} catch (JsonProcessingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
In application. properties files I have set the values :
#expand param
api.expand.value: expand
I am new to this, can any one tell me where I am making the mistake:
Getting the following error:
java.lang.IllegalArgumentException: Either targetObject or targetClass for the field must be specified
at org.springframework.util.Assert.isTrue(Assert.java:121)
at org.springframework.test.util.ReflectionTestUtils.setField(ReflectionTestUtils.java:178)
at org.springframework.test.util.ReflectionTestUtils.setField(ReflectionTestUtils.java:107)
at org.springframework.test.util.ReflectionTestUtils.setField(ReflectionTestUtils.java:91)
at com.aig.rs.products.orchestrator.api.utils.ExpandParamTest.setup(ExpandParamTest.java:29)
#Value is a spring annotation, it depends on the Spring Context to function. If you want #Value to read the value from your application properties then you need to convert your unit test into a #SpringBootTest. Take a look at this tutorial to understand a bit more about Spring Test.
You're also using ReflectionTestUtils.setField(expandQueryParam, "expandParam", "expand"); which will just set a value to this field, not read it from properties. This exception you're seeing is because expandQueryParam is null, these annotations #Spy and #InjectMocks are Mockito annotations and for them to initialize your object you need to enable mockito annotations, you can do this by adding #ExtendWith(MockitoExtension.class) on top of your class or using MockitoAnnotations.initMocks(this) in setUp method.
I don't think you need mockito to test this class, in my opinion going for a Spring Test would be a better option this way you can also test the reading of the property key.

JUnit: test that no error is logged

I am trying to test something like this:
try {
logger.info("message");
//do something
} catch(Exception e) {
logger.error(errorMessage);
}
I know that it's not a good practice to catch an Exception, but there is some legacy code and there is no time for refactoring.
So, I write an unit test so that a NullPointerException will be thrown inside try block, but now I don't know how to write the assert line(obviously, unit test have to fail all the time).
Please notice that I can`t use:
final Logger logger = LogManager.getLogger(AnaliticsService.class);
final Appender mockAppender = mock(Appender.class);
logger.addAppender(mockAppender);
final ArgumentCaptor<LoggingEvent> captor = ArgumentCaptor.forClass(LoggingEvent.class);
Log4jConfigHelper.getInstance().bufferConfiguration();
verify(mockAppender, times(x)).doAppend(captor.capture());
because I don`t know how many messages are logged when UT is running.
You should try to make a Mock for LoggerFactory.
First annotate your TestClass with:
#RunWith(PowerMockRunner.class)
#PrepareForTest({YourController.class, LoggerFactory.class})
Then make a test, which calls needed method and veryfies errors:
#Test
public void testErrorLogging() throws Exception {
mockStatic(LoggerFactory.class);
Logger logger = mock(Logger.class);
when(LoggerFactory.getLogger(any(Class.class))).thenReturn(logger);
YourController controller = new YourController();
controller.someMethod();
verify(logger).error(anyString());
}
Log messages are part of the user interface of your code. Code that does computations should not make assumptions about the manner in which log messages are made available to the user, the text and language of the log messages, or even whether messages are communicated as text (rather than, say, a graphical means). So computational code should delegate to an associated logger class (in the UI/presentation layer) that hides all those details.
If the computational code only requires that the associated logger conforms to an interface, and uses dependency injection for being associated with a logger class, it is easy to mock the logger to examine whether the computational code has requested logging.
So if the code to be tested is like this::
public class MyService
{
private final MyServiceLogger logger;
MyService(MyServiceLogger logger)
{
this.logger = Objects.requireNonNull(logger);
}
public void processFile(Path path) {
...
try{
...
} catch (EOFException e) {
logger.logUnexpectedEOF(path);
}
}
}
public interface MyServiceLogger
{
public logUnexpectedEOF(Path path);
}
public class MyServiceTextLogger implements MyServiceLogger
{
private final Logger textLogger = LogManager.getLogger(MyService.class);;
#Override
public logUnexpectedEOF(Path path) {
textLogger.error("unexpected EOF for file {}",path);
}
}
You can test it like this:
public class MyServiceTest
{
private static class MockMyServiceLogger implements MyServiceLogger
{
private Path path;
private int nCalls_logUnexpectedEOF;
#Override
public logUnexpectedEOF(Path path) {
++nCalls_logUnexpectedEOF;
this.path = path;
}
void assertCalled_logUnexpectedEOF(int nCalls, Path path) {
assertEquals("Called logUnexpectedEOF, nCalls", nCalls, nCalls_logUnexpectedEOF);
assertEquals("Called logUnexpectedEOF, path", path, this.path);
}
}
#Test
public void processFile_unexpectedEOF() {
Path testPath = ...
...
MockMyServiceLogger mockLogger = new MockMyServiceLogger();
MyService service = new MyService(mockLogger);
service.processFile(testPath);
mockLogger.assertCalled_logUnexpectedEOF(1, testPath);
}
#Test
public void processFile_OK() {
Path testPath = ...
...
MockMyServiceLogger mockLogger = new MockMyServiceLogger();
MyService service = new MyService(mockLogger);
service.processFile(testPath);
mockLogger.assertCalled_logUnexpectedEOF(0, null);
}
}
I write an unit test so that a NullPointerException will be thrown inside try block, but now I don't know how to write the assert line(obviously, unit test have to fail all the time).
You don't need to check for an exception this way. A test which throws an Exception fails.
} catch(Exception e) {
logger.error(errorMessage, e);
throw e; // report the error to the test
}
Note: when to throw an error to the testing framework it will log/print it so I suspect you don't need to be catching it in the first place.

How to access a file in the .war classpath during unit tests?

I have a web application project, and I'm trying to unit test a method that creates a file using a FreeMarker template. My method createFile() should take a MyFile type - that contains the File name to create and the rootMap FreeMarker needs and the template name - and create a file using the template I provide.
I am following the Freemarker manual to set a Template Loader. The problem is, I'm using the TemplateLoader setClassForTemplateLoading(Class, String) method to find the template path. This template loader uses the Class.getResource() to get the classpath.
But, since I'm using Maven, I have my java code in /src/main/java, my template in /src/main/webapp/templates/ and my test code in /src/test/java. Therefore, my Class.getResource("/") (root classpath) always returns <PATH_TO_PROJECT>/target/test-classes/.
Since I will be deploying a war, I cannot use the setDirectoryForTemplateLoading(File). Also, since I'm testing my app I don't have a ServletContext to use with setServletContextForTemplateLoading(Object, String).
How can I access my template folder from the test case?
Here's a simplified example of my test code (I use mockito to mock the behaviour of the MyFile class):
private MyFile myFile;
private FileGenerator fileGenerator;
#Before
public void setUp() {
myFile = new MyFile(...);
fileGenerator = new FileGenerator(myFile, ...);
}
#Test
public void shouldCreateFile() {
final MyFile mockedMyFile = spy(file);
final Map<String, Object> rootMap = new HashMap<String, Object>();
// populates rootMap with stuff needed for the Template
// mocking method return
when(mockedMyFile.getRootMap()).thenReturn(rootMap);
// replacing the MyFile implementation with my Mock
fileGenerator.setMyFile(mockedMyFile);
// calling the method I want to test
fileGenerator.createFile();
assertTrue(MyFile.getFile().exists());
}
And here is a simplification of the code I'm testing:
public void createFile() {
final Configuration cfg = new Configuration();
cfg.setClassForTemplateLoading(getClass(), "templates/");
try {
myFile.getFile().createNewFile();
final Template template = cfg.getTemplate("template.ftl");
final Writer writer = new FileWriter(myFile.getFile());
template.process(myFile.getRootMap(), writer);
writer.flush();
writer.close();
}
// exception handling
}
I applied Charles Forsythe's suggestion, it worked out fine.
I just added a templateLoader member to the FileGenerator class, with its own getter and setter.
Next, in my createFile method, I use the method setTemplateLoader(TemplateLoader) from the Configuration class, as such:
public void createFile() {
final Configuration cfg = new Configuration();
// Changed
cfg.setTemplateLoader(templateLoader);
// the rest
}
Finally, I just create a template loader for my test:
#Test
public void shouldCreateFile() {
final MyFile mockedMyFile = spy(file);
final Map<String, Object> rootMap = new HashMap<String, Object>();
TemplateLoader templateLoader = null;
try {
templateLoader = new FileTemplateLoader(new File("<PATH TO TEMPLATE FOLDER>"));
} catch (final IOException e) {
e.printStackTrace();
}
gerador.setTemplateLoader(templateLoader);
// the rest
}
And problem solved. In my production code, I use ClassTemplateLoader instead of FileTemplateLoader.

Categories