views:

94

answers:

3

I'm testing a method that logs warnings when something went wrong and returns null.
something like:

private static final Logger log = Logger.getLogger(Clazz.class.getName());
....
if (file == null || !file.exists()) {
  // if File not found
  log.warn("File not found: "+file.toString());
} else if (!file.canWrite()) {
  // if file is read only
  log.warn("File is read-only: "+file.toString());
} else {
  // all checks passed, can return an working file.
  return file;
}
return null;

i'd like to test with junit that a warning was issued, in addition to returning null, in all cases (e.g. file not found, file is read-only).
any ideas?
thanks, asaf :-)


UPDATE

My implementation of Aaron's answer (plus peter's remark):

public class UnitTest {
...

@BeforeClass
public static void setUpOnce() {
  appenders = new Vector<Appender>(2);
  // 1. just a printout appender:
  appenders.add(new ConsoleAppender(new PatternLayout("%d [%t] %-5p %c - %m%n")));
  // 2. the appender to test against:
  writer = new StringWriter();
  appenders.add(new WriterAppender(new PatternLayout("%p, %m%n"),writer));
}

@Before
public void setUp() {
  // Unit Under Test:
  unit = new TestUnit();
  // setting test appenders:
  for (Appender appender : appenders) {
    TestUnit.log.addAppender(appender);
  }
  // saving additivity and turning it off:
  additivity = TestUnit.log.getAdditivity();
  TestUnit.log.setAdditivity(false);
}

@After
public void tearDown() {
  unit = null;
  for (Appender appender : appenders) {
    TestUnit.log.removeAppender(appender);
  }
  TestUnit.log.setAdditivity(additivity);
}

@Test
public void testGetFile() {
  // start fresh:
  File file;
  writer.getBuffer().setLength(0);

  // 1. test null file:
  System.out.println(" 1. test null file.");
  file = unit.getFile(null);
  assertNull(file);
  assertTrue(writer.toString(), writer.toString().startsWith("WARN, File not found"));
  writer.getBuffer().setLength(0);

  // 2. test empty file:
  System.out.println(" 2. test empty file.");
  file = unit.getFile("");
  assertNull(file);
  assertTrue(writer.toString(), writer.toString().startsWith("WARN, File not found"));
  writer.getBuffer().setLength(0);
}

thanks guys,

+1  A: 

Instead of calling log4j directly, use a protected method in your class.

Something like:

protected void log(String message, Level level)
{
    //delegates to log4j
}

Then create a subclass of the class under test that oevrrides this method, so that you can verify it is being called as expected.

class MyTest extends <class under test>
{
    boolean somethingLogged = false;
    protected void log(String message, Level level)
    {
        somethingLogged = true;
    }
}

and then assert based on somethingLogged. You can add conditional logic in the overriding method t test based on expected message/level.

You could go further and record all the invocations, and then search through the logged messages, or check they were logged in the right order etc...

Visage
This is good as a last resort, but it is better to use the existing Log4J facilities - that is more portable and reusable.
Péter Török
@visage: 10x, but i'd like to keep my test the less invasive i can. also, your solution is very verbose, and would require a central facility for logging and a lot of coding IMO.
Asaf
+6  A: 

In the setup of the unit test:

  1. Get the same logger
  2. Make it non-additive
  3. Add an appender which remembers the messages in a list:

    public TestAppender extends AppenderSkeleton {
        public List<String> messages = new ArrayList<String>();
    
    
    
    public void doAppend(LoggingEvent event) {
        messages.add( event.getMessage().toString() );
    }
    
    }
  4. Add the appender to the logger

Now you can call your code. After the test, you will find all log messages in the list. Add the log level if you want (messages.add( event.getLevel() + " " + event.getMessage() );).

In tearDown(), remove the appender again and enable additivity.

Aaron Digulla
Even better to just make messages a list of LoggingEvents - that way you can process them however you want, rather than dealing withe the string representation.
Visage
@Peter. Aaron: thanks a lot. I've used a combination of your answers.
Asaf
@Visage: comparing strings make test more readable IMHO
Asaf
You're right - but you should do the compare after the functionality has run. Storing LoggingEvents doesnt preculude you using their toString method in your Asserts, while just storing their string representation may lose data.
Visage
There is nothing preventing `event` from changing after `doAppend()` was called. `toString()` will make sure you get a snapshot.
Aaron Digulla
+3  A: 

An alternative to Aaron's solution would be to configure a WriterAppender with an attached StringWriter. At the end of the test, you can verify the contents of the log output string.

This is a bit easier to implement (no need for custom code), however is less flexible with regards to checking the results, as you only get the output as plain text. In some cases that may make it more difficult to verify the output than with Aaron's solution.

Péter Török