Mocking a String-class method in Swift - swift

I've got some Swift 2.0 code, for which I'm trying to achieve 100% code coverage. I'm doing some JSON handling, part of which looks like so:
guard let jsonData = jsonText.dataUsingEncoding(NSUTF8StringEncoding) else {
throw ErrorCode.JSONEncodingFailure
}
I don't think there's a real-world case in which any string can't be encoded as UTF-8, but I don't want to open myself up to a crashing error either, so that code must remain. How can I mock the jsonText object to return nil for dataUsingEncoding()?
The closest I've come is to subclass NSString like so:
public class BadString: NSString {
public override var length: Int {
get {
return 5
}
}
public override func characterAtIndex(index: Int) -> unichar {
return 0
}
public override func dataUsingEncoding(encoding: NSStringEncoding) -> NSData? {
return nil
}
}
Here's the problem, though. In order for my mock implementation to be used, I have to define jsonText (a function parameter) as an NSString, rather than String, which feels wrong for an all-Swift codebase. With it defined as a Swift String, I have to cast my BadString to that type, and it uses the String implementation instead of my own.
Is there another (clean) way to achieve this?

You will be hard-pressed to find a string that cannot be encoded using UTF-8! As long as you know that is the encoding you will be using, I would suggest that you not worry about testing the "encoding failure" case.
However, if you still desire to test it then I recommend making one of the following changes in order to allow you to do so:
(1) change the way you are thinking about the 'failure': if you know that the string you are encoding will always be non-empty, then broaden the guard to also require that the encoded data has length > 0, e.g. =>
guard let jsonData = jsonText.dataUsingEncoding(NSUTF8StringEncoding)
where jsonData.length > 0 else {
throw ErrorCode.JSONEncodingFailure
}
...using this idea, you can now use an empty string for jsonText and trigger this code path (assuming that an empty string would also satisfy your definition of 'failure' here)
(2) store your string encoding value in a variable (let's call it stringEncoding) that you can access during your test setup, and then test this using incompatible values for jsonText and stringEncoding, e.g. =>
var jsonText = "🙈"
let stringEncoding = NSASCIIStringEncoding
...I guarantee that jsonText.dataUsingEncoding(stringEncoding) will return nil in this case :)
Happy Testing! I hope this helps!

Related

Swift: if is let redundancy

I just joined a project that has a lot of existing code. The previous programmer was perhaps unfamiliar with Swift or began development in the early stages of the Swift language. They seemed to be using the if let statement in an odd way. They seemed to want to use the statement as a if is let. Before I edit the code I would like to know if there is any valid use for this:
// In JSON parser
if value is String, let string = value as? String {
document.createdBy = string
}
First checking if value is of type String seems redundant to me. Doesn't Swift check for this in the let string = value as? String portion of the statement?
QUESTION
Why would this need to be checked twice? Or would there be a reason for this?
You're correct, this is redundant. If value is not a string, then value as? String would return nil, and the conditional binding would fail.
To check the type, and not use the casted result:
if value is String {
// Do something that doesn't require `value` as a string
}
To check the type and use the result:
if let value = value as? String { // The new name can shadow the old name
document.createdBy = value
}
Doing both makes no sense.

Swift string interpolation at runtime

I'm looking to implement some simple string templating in Swift and am wondering what the built-in options are for replacing macros in a string with values at runtime.
It would be awesome if there were a way for me to use the string interpolation syntax "\(variable)" at runtime, but I'm guessing these are actually parsed at compile time since the macros can contain actual code.
I've also found a String constructor that accepts a format string in Objective-C style, using %#, etc.
let myString = String(format: "Hello %#", name)
That could work, though I like the syntax less. I'm just wondering if there are better approaches I should take, or if it would be better to just write my own.
Being able to execute commands (like operators or method/property calls) within the macro would be awesome, but not required (and considering how static a language Swift is, not expected).
import Foundation
let serverName = "Fantastic"
var resultString = "We are using $SERVERNAME$"
if let range = resultString.rangeOfString("$SERVERNAME$") {
resultString.replaceRange(range, with: serverName)
}
You could, of course, implement a String extension if you are going to be using this regularly.
extension String {
func replaceString(sourceString:String, withNewElements newElements:String) throws {
//implementation left for the reader
let userInfo = ["missingString": sourceString,
"message": "Substring '\(sourceString)' not found in '\(self)'"]
throw NSError(domain: "ReplaceStringFailure", code: 1, userInfo: userInfo)
}
}

How to use Strings as Printables in Swift

I'm trying to write a function that takes a parameter of type Printable:
func logMessage(message: Printable) {
// ...
}
Strangely, this doesn't work as expected when passing in Strings.
This doesn't compile:
logMessage("some string \(someVariable)")
// Neither does this:
let aString = "aString"
logMessage(aString)
This however compiles:
logMessage("A string")
// This works too:
let aString: Printable = "a string"
logMessage(aString)
This is quite confusing. It seems that in some cases String implements Printable and in others not.
In addition, it seems that string interpolation always produces a String that does not implement Printable. This crashes at runtime with a cast error:
let aString = "a string"
let interpolatedString = "contains \(aString)"
Any idea what's going on here?
You're right that String doesn't conform to Printable. The reason this compiles:
let aString: Printable = "Ceci n'est pas une String"
is that you aren't creating a String with that literal – you're creating an NSString (which is Printable).
Generally, in Swift, it’s usually better to write generic functions constrained by protocols. So instead of
func logMessage(message: Printable) {
// ...
}
you would probably be better off writing:
func logMessage<T: Printable>(message: T) {
// ...
}
This approach has a number of advantages – better type-safety and avoiding type erasure, more performant etc. You can read more about this stuff here.
But you'll still hit a problem because you can't pass in a String. You have two options here. First, just don't constraint it at all:
func logMessage<T>(message: T) {
// ...then use toString(message) to create a String if you need one,
// or use string interpolation or print()
}
This will work with String, and in fact will also work with anything that isn’t Printable as well (though you'll get quite a unhelpful output involving the mangled classname).
Or, you could use Streamable which strings do conform to:
func logMessage<T: Streamable>(message: T) {
println(message)
}
let s: String = "hello"
logMessage(s)
I think I read a while back on twitter one of the Swift team mention that the reason String doesn't conform to Printable is exactly because they didn't want people using Printable directly like this and that it’s better to always use toString or similar.

Convert or cast object to string

how can i convert any object type to a string?
let single_result = results[i]
var result = ""
result = single_result.valueForKey("Level")
now i get the error: could not assign a value of type any object to a value of type string.
and if i cast it:
result = single_result.valueForKey("Level") as! String
i get the error:
Could not cast value of type '__NSCFNumber' (0x103215cf0) to 'NSString' (0x1036a68e0).
How can i solve this issue?
You can't cast any random value to a string. A force cast (as!) will fail if the object can't be cast to a string.
If you know it will always contain an NSNumber then you need to add code that converts the NSNumber to a string. This code should work:
if let result_number = single_result.valueForKey("Level") as? NSNumber
{
let result_string = "\(result_number)"
}
If the object returned for the "Level" key can be different object types then you'll need to write more flexible code to deal with those other possible types.
Swift arrays and dictionaries are normally typed, which makes this kind of thing cleaner.
I'd say that #AirSpeedVelocity's answer (European or African?) is the best. Use the built-in toString function. It sounds like it works on ANY Swift type.
EDIT:
In Swift 3, the answer appears to have changed. Now, you want to use the String initializer
init(describing:)
Or, to use the code from the question:
result = single_result.valueForKey("Level")
let resultString = String(describing: result)
Note that usually you don't want valueForKey. That is a KVO method that will only work on NSObjects. Assuming single_result is a Dictionary, you probably want this syntax instead:
result = single_result["Level"]
This is the documentation for the String initializer provided here.
let s = String(describing: <AnyObject>)
Nothing else is needed. This works for a diverse range of objects.
The toString function accepts any type and will always produce a string.
If it’s a Swift type that implements the Printable protocol, or has overridden NSObject’s description property, you’ll get whatever the .description property returns. In the case of NSNumber, you’ll get a string representation of the number.
If it hasn’t, you’ll get a fairly unhelpful string of the class name plus the memory address. But most standard classes, including NSNumber, will produce something sensible.
import Foundation
class X: NSObject {
override var description: String {
return "Blah"
}
}
let x: AnyObject = X()
toString(x) // return "Blah"
"\(x)" // does the same thing but IMO is less clear
struct S: Printable {
var description: String {
return "asdf"
}
}
// doesn't matter if it's an Any or AnyObject
let s: Any = S()
toString(s) // reuturns "asdf"
let n = NSNumber(double: 123.45)
toString(n) // returns "123.45"
n.stringValue // also works, but is specific to NSNumber
(p.s. always use toString rather than testing for Printable. For one thing, String doesn’t conform to Printable...)
toString() doesn't seem to exist in Swift 3 anymore.
Looks like there's a failable initializer that will return the passed in value's description.
init?(_ description: String)
Docs here https://developer.apple.com/reference/swift/string/1540435-init

Convert String to UnsafeMutablePointer<char_t> in Swift

I'm working with a third party c API I'm trying to call one of the functions with a simple string. Something like this:
some_c_func("aString");
I get a build error:
Type 'UnsafeMutablePointer<char_t>' does not conform to protocol 'StringLiteralConvertible'
I've seen some suggestions to use utf8 on String or similar conversions, which gets nearly there, but with the following error:
some_c_func("aString".cStringUsingEncoding(NSUTF8StringEncoding));
'UnsafePointer<Int8>' is not convertible to 'UnsafeMutablePointer<char_t>'
How can I create an UnsafeMutablePointer?
It all depends on what char_t is.
If char_t converts to Int8 then the following will work.
if let cString = str.cStringUsingEncoding(NSUTF8StringEncoding) {
some_c_func(strdup(cString))
}
This can be collapsed to
some_c_func(strdup(str.cStringUsingEncoding(NSUTF8StringEncoding)!))
WARNING! This second method will cause a crash if func cStringUsingEncoding(_:) returns nil.
Updating for Swift 3, and to fix memory leak
If the C string is only needed in a local scope, then no strdup() is needed.
guard let cString = str.cString(using: .utf8) else {
return
}
some_c_func(cString)
cString will have the same memory lifecycle as str (well similar at least).
If the C string needs to live outside the local scope, then you will need a copy. That copy will need to be freed.
guard let interimString = str.cString(using: .utf8), let cString = strdup(interimString) else {
return
}
some_c_func(cString)
//…
free(cString)
it may be simpler than that - many C APIs pass strings around as char * types, and swift treats these as unsafe.
try updating the C API (good) or hack it's header files (bad) to declare these as const char * instead.
in my experience this allows you to pass standard swift String types directly to the C API.
apparently a constant is required, in order to conform to the protocol.
I haven't tried passing strings like that, but I have a C function that I call from Swift, that takes a lot more parameters than shown here, among which is a reference to a Swift C typecast buffer to hold an error string. The compiler doesn't complain and the function call works. Hopefully this will steer you closer to the answer and you can provide an update with the final answer or someone else can.
var err = [CChar](count: 256, repeatedValue: 0)
var rv = somefunc((UnsafeMutablePointer<Int8>)(err))
if (rv < 0) {
println("Error \(err)")
return
}