1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Charlesでテストしにくいエラーを再現する

Posted at

はじめに

これはフェンリル デザインとテクノロジー Advent Calendar2025 9日目の記事です。

アプリ開発において、起こったエラーの種類によってその後の処理を分けることがよくあります。
しかし、自分では再現しにくいエラーが起こった際の処理はどう確認すればいいでしょうか?
そんな時に活躍するCharlesについて説明しようと思います。

Charlesとは

Charlesとは、Webやアプリ開発において、PCとインターネット間のHTTP/HTTPS通信の内容を詳細に監視、記録、そして改変できるデバッグ用のプロキシツールです。
つまり、実際にスマホがどんな情報を送受信しているのか確認し、書き換えることができます。

今回やること

Charlesは最初のセットアップが少し面倒なのですが、この記事では具体的な使い方にフォーカスしたいので割愛させていただきます💦
簡単にswiftでAPI通信を行うコードを作成し、その内容を書き換えてエラー画面を表示させようと思います。
今回作成したコードでは、処理が成功した場合はsuccessという文字列を、失敗した場合は定義したエラーメッセージを表示させます。

コードを用意

以下は今回実行するコードです。
iOSエンジニアではない人でもわかるようにコメント文で解説しましたのでぜひご覧ください。

実行するコード
import SwiftUI

struct ContentView: View {
    @State var dog = Dog()  // 表示させるわんちゃんの情報を作成しています。最初は何も入っていません。

    var body: some View {
        VStack(spacing: 20) {
            Button {
                fetchDog()  // このボタンをタップすることで通信が始まります。
            } label: {
                Text("Dog API 呼び出し")
                    .bold()
            }

            Group {
                HStack {
                    Text("status:")
                    Text(dog.viewStatus)  // viewStatusという変数を表示しています。詳しくは下の方にあります。
                        .bold()
                }

                AsyncImage(url: URL(string: dog.url)) { phase in
                    if let image = phase.image {
                        // 通信が成功し正しい画像のURLが入手できた場合、わんちゃんの画像を表示します。
                        image
                            .resizable()
                            .aspectRatio(contentMode: .fit)
                    } else {
                        // 通信中とエラーの時はグレーの背景を表示させます。
                        Color.gray.opacity(0.2)
                    }
                }
                .frame(height: 400, alignment: .center)
            }
            .padding(.horizontal, 20)
            .frame(width: UIScreen.main.bounds.width, alignment: .leading)
        }
    }

    private func fetchDog() {
        dog.reset() // 一旦わんちゃんの情報をリセットします。

        Task {
            do {
                let response = try await callAPI() // DogAPIを実行するcallAPI()関数を呼び出しています。
                
                // 通信が成功した場合、取得した画像URLと取得したsuccessという文字列をわんちゃんの情報にセットします。
                dog.url = response.message
                dog.status = response.status
            } catch let error as DogAPIError {
                // 通信が失敗した場合、エラー情報をセットしています。
                dog.errorState = error
            } catch {
                dog.errorState = .unknownError
            }
        }
    }

    /// DogAPIを実行する関数です。
    /// この中で通信を行い、エラーだった場合はそれぞれのエラーに振り分けています。
    private func callAPI() async throws -> DogResponse {
        let urlString = "https://dog.ceo/api/breeds/image/random"
        guard let url = URL(string: urlString) else {
            // APIのURLが正しくない形だった場合、このエラーを投げます。
            throw DogAPIError.invalidURL
        }

        let data: Data
        do {
            // 実際にはここで通信を行っている
            (data, _) = try await URLSession.shared.data(from: url)
        } catch {
            // 通信中にエラーが発生した場合、ネットワークのエラーとする。
            throw DogAPIError.networkError
        }

        guard !data.isEmpty else {
            //MARK: 取得したデータが何もなかった時、.dataNotFoundというエラーを投げる
            // この記事ではこのエラーを試します。
            throw DogAPIError.dataNotFound
        }

        do {
            let decoder = JSONDecoder()
            let dog = try decoder.decode(DogResponse.self, from: data)
            return dog
        } catch {
            // データを正しく変換できなかったときにこのエラーを投げます。
            throw DogAPIError.decodingFailed
        }
    }
}

struct Dog {
    var url: String = ""
    var errorState: DogAPIError? = nil
    var status: String = ""
    
    /// この変数を画面では表示します。
    /// 通信に成功した場合場合はAPIから受け取ったsuccessという文字列を表示させ、
    /// 失敗した場合はエラーメッセージを表示させます。
    var viewStatus: String {
        if let errorState = errorState {
            errorState.errorMessage
        } else {
            status
        }
    }

    mutating func reset() {
        url = ""
        errorState = nil
        status = ""
    }
}

struct DogResponse: Decodable {
    let message: String
    let status: String
}

enum DogAPIError: Error {
    case invalidURL
    case networkError
    case dataNotFound
    case decodingFailed
    case unknownError

    // ここでそれぞれのエラーに対応するエラーメッセージを設定してます。
    var errorMessage: String {
        switch self {
        case .invalidURL:
            return "不正なURL"
        case .networkError:
            return "ネットワークエラー"
        case .dataNotFound:
            return "データがありません"
        case .decodingFailed:
            return "デコードに失敗しました"
        case .unknownError:
            return "不明なエラー"
        }
    }
}

用意したコードを実行してみるとこのような挙動になります。

Simulator Screen Recording - iPhone 16 Pro - 2025-12-08 at 18.31.42.gif
永遠に見ていられますね🕺✨

Charlesで通信を確認

それではいよいよCharlesを使って通信を確認してみます。
まず、画面上部のメニューにある Proxy > SSL Proxying Setting... から今回確認したい通信の設定をします。
Hostには、今回のAPI通信のホスト名である、dog.ceoを設定します。
Portには、*を指定して、DogAPIへの全ての通信を見られるようにします。

image.png

そしてアプリからDogAPIを呼び出すボタンをタップすると...

image.png

このように、アプリが受け取った通信情報をみることができました👏
実際の通信を見ることで、不具合があった際サーバーに問題があるのか、アプリ側に問題があるのかについて知る手掛かりにもなるのでとても便利です!

しかし今回試したいのは、自分では再現しにくいエラーが起こった際の処理ですので、ここから通信内容を書き換えてエラーを再現してみようと思います。

通信内容を書き換える

今回書いたコードの中で、取得したデータが0件だった際に起こるエラー(.dataNotFound)を引き起こしてみようと思います。
そのためには通信内容を受け取った時に、データを全て削除してアプリ側に渡すと再現できるのではないかと考えました。

そこで役に立つのが Breakpoint(ブレイクポイント) という機能です。
この機能を使うと、指定した通信が Charles に到達したタイミングで処理を一時停止し、その内容を書き換えることができます。
画面上部のメニューから Proxy > Breakpoints Settings... を開き、一時停止させたい通信とタイミングを設定します。

image.png

ここでは通信の情報に加え、Responseにチェックを入れることで情報を受け取った時に処理を止めるようにしています。
設定後アプリからDogAPIを呼び出すボタンをタップすると...

image.png

情報を取得したタイミングでちゃんと処理が止まってくれました✨
この画面で、アプリに渡す通信情報を編集することができます。

画面上部のEdit Responseタブからデータを書き換えます。
今回はデータを全て削除したいので、{}ごと全て削除してExcuteボタンを押します。

すると...

期待通り.dataNotFoundのエラーメッセージが表示されました🙌
このように通常では再現できないようなエラーをテストすることで、アプリの品質の向上を図ることができると思います!
ぜひみなさんご活用ください🙇

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?