I've been working on a logging framework as of recent, and I'm allowing the user to provide an NSPipe
for the logs to go down if they don't want the log messages to go to the console.
The Logger
can be enabled or disabled, and when disabled, should not send any data down the pipe.
It's easy to check to see whether or not data has been sent down the pipe when the logger is enabled, as availableData
immediately contains the data, and the NSFileHandleDataAvailableNotification/readabilityHandler block is triggered.
However, if you were to test that data was not sent down the pipe, and tried applying the same idea that availableData
would not contain any data, the tests will timeout as availableData
only returns once data has been received.
I came up with one solution which would be to listen out for the notification, and if it hasn't been received after X seconds, fulfil an expectation that the notification hasn't been received, with the premise that the expectation will be fulfilled twice (causing the test to fail) if a notification is received.
The problem with this approach is that if, for some reason, the pipe does receive some data, once the expectation is fulfilled in the readabilityHandler
block, the test succeeds immediately, and the expectation is not fulfilled for a second time:
func testDisabledLoggerDoesntLog() {
let logger = Logger()
let pipe = NSPipe()
logger.pipe = pipe
logger.enabled = false
let expectation = expectationWithDescription("handler not triggered")
logger.pipe!.fileHandleForReading.readabilityHandler = { handler in
expectation.fulfill()
}
logger.debug("Test message")
fulfillAfter(expectation, time: 2)
waitForExpectationsWithTimeout(3, handler: nil)
}
func fulfillAfter(expectation: XCTestExpectation, time: Double = 4) {
let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(time * Double(NSEC_PER_SEC)))
dispatch_after(delayTime, dispatch_get_main_queue()) {
expectation.fulfill()
}
}
What would a better approach be that will lead to a failing test if the pipe receives data, and a passing test if it doesn't?
As outlined in the comments, I've found a separate solution which writes arbitrary data to the pipe, executes the log (which may or may not result in more data being written to the pipe), and then retrieves the data from the pipe's availableData
.
If the availableData
's length is equal to that of the arbitrary data, then it is clear that the log has not been written to the pipe. Otherwise if the length is greater than the data that the test has written, the log has indeed been executed and written its data to the pipe.
let logger = Logger()
let pipe = NSPipe()
logger.pipe = pipe
logger.enabled = false
logger.useCurrentThread = true // The logs generally use a background thread to prevent interrupting the main queue. In this test, the current thread must be used in order for it to be synchronous.
let writtenData = "written data".dataUsingEncoding(NSUTF8StringEncoding)!
logger.pipe!.fileHandleForWriting.writeData(writtenData)
logger.debug("Test message")
XCTAssertEqual(logger.pipe!.fileHandleForReading.availableData.length, writtenData.length)