0

I am creating mock to check logger functionality. For that i have interface and create mock for that. But its not working. Any help appreciated.

This is my interface

public interface ILoggerMock {

    public void write(String text) throws Throwable;
}

And this is my Test class

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.when;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.List;

import org.junit.Test;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

public class LoggerMock {

    @Test
    public void out() throws Throwable {

        ILoggerMock iLoggerMock = Mockito.mock(ILoggerMock.class);

        Mockito.doAnswer(new Answer<Void>() {
            public Void answer(InvocationOnMock invocationOnMock) throws Throwable, IOException {
                try {
                    System.out.println("CalculatorMock.out().new Answer() {...}.answer()");

                    Files.write(Paths.get("D:/log/logger.txt"), "Hello!".getBytes(), StandardOpenOption.CREATE);
                } catch (IOException e) {
                    // exception handling
                }
                return null;
            }
        }).when(iLoggerMock).write("write");

        List<String> list = Files.readAllLines(Paths.get("D:/log/logger.txt"));

        for (String line : list) {
            System.out.println(line);
        }

        //System.out.print("hello");
        assertEquals("Hello!", list.get(0));
    }

}

Problem is in mock i am not able to create file. Please help me for creating file.

Gerold Broser
  • 14,080
  • 5
  • 48
  • 107
CHiRAG
  • 222
  • 4
  • 15
  • 2
    Your code which will write the file is wrapped in an `Answer`. That will only occur when something _calls_ `write` on the mock `ILoggerMock` class somewhere. Do you pass this instance anywhere which is going to use it? – BeUndead Jun 02 '20 at 14:51
  • yes i am pasing mock into when? – CHiRAG Jun 02 '20 at 15:33
  • i am debugging the code but doAnswer() is not called or not even print message on console. – CHiRAG Jun 02 '20 at 16:06
  • 2
    That would be my point. You have set up code to be an _answer_ when that object is called. But nothing is calling it. You don't have a test case which calls that code. You don't pass it into other objects which will indirectly call it when invoked, etc. – BeUndead Jun 03 '20 at 09:26

1 Answers1

1

First of all please see Is “throws Throwable” good practice regarding your Interface declaration.

As @BeUndead mentions in the comments to the question all Mockito methods in conjunction with when(...) just prepare a mock what to do when one of its methods is invoked later.

Mockito.doAnswer(...).when(iLoggerMock).write("write"); in your code defines what's done once the mock's write() method is invoked with the argument "write". So, to actually make the things happen therein you have to invoke iLoggerMock.write("write") somewhere after. (But write isn't the text you want to write anyway, it's Hello!.)

A working code could look like:

Main class

public interface ILogger {

    public void write( String text ) throws Exception;

    public Path getLog();
}

Test classes

abstract class AbstractLoggerTest {

    protected Path log;

    protected void testWrite( ILogger logger, String text ) throws Exception {

        log = logger.getLog(); // here happens what has been prepared in when(...).thenReturn(...); below

        logger.write( text ); // here happens what has been prepared in doAnswer(...).when(...).write(...); below

        assertEquals( "Wrong line count", 1,
                Files.lines( log )
                        .map( line -> {
                            //System.out.println( line );  // Uncomment this if you really must.
                            // Actually we are using automated tests for this:
                            // To prevent having to trace program or test outputs manually.
                            assertEquals( "Written and read text not equal", text, line );
                            return line;
                        } ).count() );
    }

    protected String textToWrite( ILogger forLogger, Path toLog ) {

        return String.format( "Text from %s written to %s.",
                forLogger.getClass().getName(), toLog );
    }
}
public class ILoggerTest extends AbstractLoggerTest {

    private static final String LOG_FILE = "target/interface.log.txt";

    @Mock
    private ILogger logger;

    @Before
    public void before() {

        MockitoAnnotations.initMocks( this );
        log = Paths.get( LOG_FILE );
        when( logger.getLog() ).thenReturn( LOG ); // this prepares the mock what to do when `logger.getLog()` is invoked later
    }

    @Test
    public void testWrite() throws Exception {

        final String TEXT_TO_WRITE = textToWrite( logger, log );

        doAnswer( new Answer<Void>() {

            @Override
            public Void answer( InvocationOnMock invocation ) throws IOException {

                Object[] args = invocation.getArguments();
                Files.writeString( log, (String) args[0] );
                return null;
            }
        } ).when( logger ).write( any() ); // this prepares the mock what to do when `logger.write()` is invoked
                                           // with any argument later (String, in this case. The argument type 
                                           // will get inferred by the compiler.) 


        testWrite( logger, TEXT_TO_WRITE );
        verify( logger ).write( TEXT_TO_WRITE );
    }
}

The class AbstractLoggerTest has the advantage that if an ILogger implementation is created in the future [constructors and setters/getters omitted for brevity]:

Main classes

public abstract class AbstractLogger implements ILogger {

    protected Path log;

    @Override
    public void write( String text ) throws IOException {

        Files.writeString( log, text );
    }

    @Override
    public Path getLog() {

        return log;
    }
}
public class TextLogger extends AbstractLogger {

    {
        log = Paths.get( "target/log.txt" );
    }
}

its test class would simply look like:

public class TextLoggerTest extends AbstractLoggerTest {

    @Test
    public void testWrite() throws Exception {

        ILogger logger = new TextLogger();
        testWrite( logger, textToWrite( logger, logger.getLog() ) );
    }
}
Gerold Broser
  • 14,080
  • 5
  • 48
  • 107