简体   繁体   中英

How to write a unit test for flushing a chained stream in java?

I am building a simple logger class-

public class MyLogger {

    private final PrintWriter errorWriter;

    public MyLogger(OutputStream outputStream) {
        final Writer errorStreamWriter = new OutputStreamWriter(outputStream);
        this.errorWriter = new PrintWriter(errorStreamWriter);
    }
    
    public void start() {
        errorWriter.flush();
    }
    
    public void addError(String errorMessage) {
        errorWriter.println(errorMessage);
    }
    
    public void finish() {
        errorWriter.flush();
        errorWriter.close();
    }
}

Now I want to write a unit test to test whether the streams are getting flushed or not, in short if we comment the following methods-

public void start() {   
    // errorWriter.flush();
}                       
                        
public void finish() {  
    // errorWriter.flush();
    // errorWriter.close();
}                       

then the test should fail, I do not wish to use reflection even if it provides a solution, I feel that this isn't possible as we have no control over the errorWriter and other connected streams won't be flushed when the chained stream is flushed, but still, if there is a solution, kindly let me know.

There are some possiblities how you can test this. I am using Mockito as mocking framework in the examples.

I would prefer solution 1) all other are more or less hacks, but the pattern can be useful for more dificult classes which use database connections or other expensive objects.

  1. You test against the public API of you class(like a black box). The caller did not know anything about the internal PrintWriter and has no need to know anything about it. However you can write a test to ensure the OutputStream is flushed since this is propagated (at least with standard Java Streams). This would fail if you comment out the flush in the start method.

     class MyLoggerTest { OutputStream out = mock(OutputStream.class); MyLogger myLogger = new MyLogger(out); @Test void should_call_flush_out_stream() throws IOException { myLogger.start(); verify(out).flush(); } }
  2. You change your class to have a second protected constructor which accepts a PrintWriter . The public constructor uses this one and you use a mock and the second constructor in your test for the flush. The test is similar to the one in 1) but you use a mock(printWriter.class) .

     public class MyLogger { private final PrintWriter errorWriter; public MyLogger(OutputStream outputStream) { this(new PrintWriter(new OutputStreamWriter(outputStream))); } MyLogger(PrintWriter writer) { this.errorWriter = writer; }....
  1. You did not access the PrintWriter directly from the member variable, but create an internal getter method, which can then be used in a Spy to change the internal representation.

     public class MyLogger { private final PrintWriter errorWriter; ...Constructor... PrintWriter getWriter() { return errorWriter; } public void start() { getWriter().flush(); }... }

And the test would look like this:

class MyLoggerTest {
  OutputStream out = mock(OutputStream.class);
  PrintWriter writer = mock(PrintWriter.class);
  MyLogger myLogger = spy(new MyLogger(out));

  @BeforeEach
  void setup() {
    when(myLogger.getWriter()).thenReturn(writer);
  }

  @Test
  void should_call_flush_out_stream() {
    myLogger.start();
    verify(writer).flush();
  }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM