How to test required init(coder:)?

5.9k Views Asked by At

In my custom class WLNetworkClient I had to implement such method:

required init(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

I do not need to use that, but I would like to test this to make 100% code coverage. Do you know how to achieve this?

I tried following way with no success:

let nc = WLNetworkClient(coder: NSCoder())
XCTAssertNotNil(nc)
7

There are 7 best solutions below

5
Rudolf Adamkovič On BEST ANSWER

Production code:

required init?(coder: NSCoder) {
    return nil
}

Test:

func testInitWithCoder() {
    let archiverData = NSMutableData()
    let archiver = NSKeyedArchiver(forWritingWithMutableData: archiverData)
    let someView = SomeView(coder: archiver)
    XCTAssertNil(someView)
}

Since the required initializer returns nil and does not use the coder, the above code can be simplified to:

func testInitWithCoder() {
    let someView = SomeView(coder: NSCoder())
    XCTAssertNil(someView)
}
1
Patrick On

Here is answer which should help you:

let cd = NSKeyedUnarchiver(forReadingWithData: NSMutableData())
let c = CustomTextField(coder:cd)
3
sundance On

Answer from Rudolf Adamkovič ist still working with Swift 4:

required init?(coder aDecoder: NSCoder) {
    return nil
}

func testInitWithCoder() {
    // 1. Arrange
    let archiver = NSKeyedArchiver(forWritingWith: NSMutableData())

    // 2. Action
    let viewController = ViewController(coder: archiver)

    // 3. Assert
    XCTAssertNil(viewController)
}
0
piperamirez On

I combined Rudolf's answer with Liz's suggestion and ended with the following solution:

let viewController = SomeTableViewController(
    presenter: SomePresenterMock(),
    coder: NSKeyedUnarchiver(forReadingWith: Data())) 

XCTAssert(viewController?.tableView.numberOfSections == 1)

The key here is to use NSKeyedUnarchiver(forReadingWith: Data()) as mock coder, otherwise the test crashes with NSInvalidArgumentException.

0
ChikabuZ On

I use improved Rudolf's Adamkovič answer:

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
}

func test_initWithCoder() {
    let object = SomeView()
    let data = NSKeyedArchiver.archivedData(withRootObject: object)
    let coder = NSKeyedUnarchiver(forReadingWith: data)
    let sut = SomeView(coder: coder)
    XCTAssertNotNil(sut)
}
0
Alley Pereira On

It seems that all the previous answers stopped working after Swift 5.

I managed to modify @ChikabuZ answer and make it work like this:

func testInitWithCoder() {
    // Given
    let object = SomeView()
    let data = try! NSKeyedArchiver.archivedData(withRootObject: object, requiringSecureCoding: false)
    let coder = try! NSKeyedUnarchiver(forReadingFrom: data)

    // When
    let sut = SomeView(coder: coder)

    // Then
    XCTAssertNotNil(sut)
}
0
ocodo On

Swift 5.x & macOS ≥ 10.14 - we don't want those pesky deprecation messages.

The nuts and bolts of NSCoder:

The init(coder:) expects an encoding of the same type, so it's safest to provide some matching base object, if it exists.

For example NSView probably won't like a coder containing NSWindowController.


For the question specifics, assuming: WLNetworkClient has a plain old init, we should create an instance of for coding.

We'll make a mock generator first, to avoid all that do / catch boilerplate.

// Helper function to create a mock NSCoder for testing init(coder:)
func mockCoder(for object: Any) -> NSCoder {
    do {
        // Archive the object to data
        let data = try NSKeyedArchiver.archivedData(
            withRootObject: object,
            requiringSecureCoding: false)

        // Initialize NSKeyedUnarchiver as our NSCoder concrete instance.
        // Return it to our test code.
        return try NSKeyedUnarchiver(forReadingFrom: data)
    } catch {

        // In production code, handle errors more gracefully 
        // (e.g. catch return nil and use a return type NSCoder?)
        fatalError("Error: Failed to create a mock NSCoder for \(object)")
    }
}

Use it in the test like this:

let coder: NSCoder = mockCoder(for: WLNetworkClient())

let sut = WLNetworkClient(coder: coder)

XCTAssertNotNil(sut)

No more nasty, red, coverage-missing, block. Just a nice, green, covered init(coder:).