Keychain Services API Tutorial for Passwords in Swift

In this Keychain tutorial for Swift on iOS, you’ll learn how to interact with the C language API to securely store passwords in the iOS Keychain. By Lorenzo Boaro.

4.7 (21) · 2 Reviews

Download materials
Save for later
Share
You are currently viewing page 3 of 3 of this article. Click here to view the first page.

Testing the Behavior

In this section, you’ll see how to integrate unit tests for your wrapper. In particular, you’ll test the functionalities that your wrapper exposes.

Note: If you’re new to unit tests and you want to explore the subject, check out our amazing iOS Unit Testing and UI Testing Tutorial.

Creating the Class

To create the class that will contain all your unit tests, click FileNewFile… and select iOSSourceUnit Test Case Class. On the next screen, specify the class name as SecureStoreTests, subclass XCTestCase and make sure the language is Swift. Click Next, choose the SecureStoreTests group, verify that you have selected the SecureStoreTests targets checkbox and click Create.

Xcode will prompt a dialog to create an Objective-C bridging header. Click Don’t Create to skip the creation.

Objective-C Bridging Header Dialog

Open SecureStoreTests.swift file and remove all the code within the curly braces.

Next, add the following below the import XCTest statement:

@testable import SecureStore

This gives the unit tests access to the classes and methods defined in your SecureStore framework.

Note: You might see a “No such module” error. Don’t worry, the error will go away when you get to the end of this section of the tutorial and perform the test.

Next, add the following properties at the top of SecureStoreTests:

var secureStoreWithGenericPwd: SecureStore!
var secureStoreWithInternetPwd: SecureStore!

Next, add a new setUp() method like this:

override func setUp() {
  super.setUp()
  
  let genericPwdQueryable =
    GenericPasswordQueryable(service: "someService")
  secureStoreWithGenericPwd =
    SecureStore(secureStoreQueryable: genericPwdQueryable)
  
  let internetPwdQueryable =
    InternetPasswordQueryable(server: "someServer",
                              port: 8080,
                              path: "somePath",
                              securityDomain: "someDomain",
                              internetProtocol: .https,
                              internetAuthenticationType: .httpBasic)
  secureStoreWithInternetPwd =
    SecureStore(secureStoreQueryable: internetPwdQueryable)
}

Since you test both generic and internet passwords, you create the two instances of your wrapper with two different configurations. Those configurations are the ones you developed in the previous section.

Before you forget, you’ll want to clear the state of the keychain during the tear down phase of the test so that you can start fresh for next time. Add this method to the end of the class:

override func tearDown() {
  try? secureStoreWithGenericPwd.removeAllValues()
  try? secureStoreWithInternetPwd.removeAllValues()
  
  super.tearDown()
}

Since you should isolate and execute each test independently from the others, you’re going to delete all the passwords already available in the Keychain. The execution order doesn’t matter.

It’s now time to add unit tests for generic passwords.

Testing Generic Passwords

Add the following code below tearDown():

// 1
func testSaveGenericPassword() {
  do {
    try secureStoreWithGenericPwd.setValue("pwd_1234", for: "genericPassword")
  } catch (let e) {
    XCTFail("Saving generic password failed with \(e.localizedDescription).")
  }
}

// 2
func testReadGenericPassword() {
  do {
    try secureStoreWithGenericPwd.setValue("pwd_1234", for: "genericPassword")
    let password = try secureStoreWithGenericPwd.getValue(for: "genericPassword")
    XCTAssertEqual("pwd_1234", password)
  } catch (let e) {
    XCTFail("Reading generic password failed with \(e.localizedDescription).")
  }
}

// 3
func testUpdateGenericPassword() {
  do {
    try secureStoreWithGenericPwd.setValue("pwd_1234", for: "genericPassword")
    try secureStoreWithGenericPwd.setValue("pwd_1235", for: "genericPassword")
    let password = try secureStoreWithGenericPwd.getValue(for: "genericPassword")
    XCTAssertEqual("pwd_1235", password)
  } catch (let e) {
    XCTFail("Updating generic password failed with \(e.localizedDescription).")
  }
}

// 4
func testRemoveGenericPassword() {
  do {
    try secureStoreWithGenericPwd.setValue("pwd_1234", for: "genericPassword")
    try secureStoreWithGenericPwd.removeValue(for: "genericPassword")
    XCTAssertNil(try secureStoreWithGenericPwd.getValue(for: "genericPassword"))
  } catch (let e) {
    XCTFail("Saving generic password failed with \(e.localizedDescription).")
  }
}


// 5
func testRemoveAllGenericPasswords() {
  do {
    try secureStoreWithGenericPwd.setValue("pwd_1234", for: "genericPassword")
    try secureStoreWithGenericPwd.setValue("pwd_1235", for: "genericPassword2")
    try secureStoreWithGenericPwd.removeAllValues()
    XCTAssertNil(try secureStoreWithGenericPwd.getValue(for: "genericPassword"))
    XCTAssertNil(try secureStoreWithGenericPwd.getValue(for: "genericPassword2"))
  } catch (let e) {
    XCTFail("Removing generic passwords failed with \(e.localizedDescription).")
  }
}

There’s quite a bit going on here, so breaking it down:

  1. testSaveGenericPassword() methods verifies whether it can save a password correctly.
  2. testReadGenericPassword() first saves the password then retrieves the password, checking if it’s equal to the expected one.
  3. testUpdateGenericPassword() verifies when saving a different password for the same account, the latest password is the one expected after its retrieval.
  4. testRemoveGenericPassword() tests that it can remove a password for a specific account.
  5. Finally, testRemoveAllGenericPasswords checks that all the passwords related to a specific service are deleted from the Keychain.

Since your wrapper can throw exceptions, each catch block makes the tests fail if something goes wrong.

Checking Your Work

Now it’s time to verify that everything works as expected. Select TestHost as the active scheme for your Xcode project:

Active Scheme

Press Command-U on your keyboard (or select Product ▸ Test in the menu) to perform the unit tests.

Note: You don’t need to run the app as you normally would in a tutorial. For this tutorial, you check your code by performing the unit tests.

Show the Test navigator and wait for the tests to execute. Once they’ve finished, you’ll expect all five tests to be green. Nice!

Green Tests

Next, do the same for internet passwords.

Scroll to the end of the class and just before the last curly brace add the following:

func testSaveInternetPassword() {
  do {
    try secureStoreWithInternetPwd.setValue("pwd_1234", for: "internetPassword")
  } catch (let e) {
    XCTFail("Saving Internet password failed with \(e.localizedDescription).")
  }
}

func testReadInternetPassword() {
  do {
    try secureStoreWithInternetPwd.setValue("pwd_1234", for: "internetPassword")
    let password = try secureStoreWithInternetPwd.getValue(for: "internetPassword")
    XCTAssertEqual("pwd_1234", password)
  } catch (let e) {
    XCTFail("Reading internet password failed with \(e.localizedDescription).")
  }
}

func testUpdateInternetPassword() {
  do {
    try secureStoreWithInternetPwd.setValue("pwd_1234", for: "internetPassword")
    try secureStoreWithInternetPwd.setValue("pwd_1235", for: "internetPassword")
    let password = try secureStoreWithInternetPwd.getValue(for: "internetPassword")
    XCTAssertEqual("pwd_1235", password)
  } catch (let e) {
    XCTFail("Updating internet password failed with \(e.localizedDescription).")
  }
}

func testRemoveInternetPassword() {
  do {
    try secureStoreWithInternetPwd.setValue("pwd_1234", for: "internetPassword")
    try secureStoreWithInternetPwd.removeValue(for: "internetPassword")
    XCTAssertNil(try secureStoreWithInternetPwd.getValue(for: "internetPassword"))
  } catch (let e) {
    XCTFail("Removing internet password failed with \(e.localizedDescription).")
  }
}

func testRemoveAllInternetPasswords() {
  do {
    try secureStoreWithInternetPwd.setValue("pwd_1234", for: "internetPassword")
    try secureStoreWithInternetPwd.setValue("pwd_1235", for: "internetPassword2")
    try secureStoreWithInternetPwd.removeAllValues()
    XCTAssertNil(try secureStoreWithInternetPwd.getValue(for: "internetPassword"))
    XCTAssertNil(try secureStoreWithInternetPwd.getValue(for: "internetPassword2"))
  } catch (let e) {
    XCTFail("Removing internet passwords failed with \(e.localizedDescription).")
  }
}

Notice that the code above is identical to the one previously analyzed. You’ve just replaced the reference secureStoreWithGenericPwd with secureStoreWithInternetPwd.

Select TestHost as the active scheme, if it’s not already, and press Command-U on your keyboard to test again. Now all the tests, both for generic and internet passwords, should be green.

Congratulations! You now have a working, stand-alone framework and unit tests in place.

Where to Go From Here?

In this tutorial, you made a framework wrapping the Keychain Services API and even integrated unit tests to prove your code works as expected. Amazing!

You could go a step further, sharing or distributing your code with other people following the final part of our tutorial Creating a Framework for iOS.

You can download the completed version of the project using the Download Materials button at the top or bottom of this tutorial.

If you want to learn more, check out Apple’s documentation at Keychain Services.

It’s worth noting that Keychain is not limited to passwords. You can store sensitive information like credit card data or short notes. You can also save items like cryptographic keys and certificates that you manage with Certificate, Key, and Trust Services, to conduct secure and authenticated data transactions.

What did you learn from this? Any lingering questions? Want to share something that happened along the way? You can discuss it in the forums. See you there!