Skip to content

Commit 1a5a42f

Browse files
authored
[Firebase AI] Add GenerativeModel tests using Dev API mock responses (#14816)
1 parent bf8a14d commit 1a5a42f

7 files changed

+721
-270
lines changed

FirebaseAI.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ Build AI-powered apps and features with the Gemini API using the Firebase AI SDK
6363
unit_tests_dir + 'Snippets/**/*.swift',
6464
]
6565
unit_tests.resources = [
66-
unit_tests_dir + 'vertexai-sdk-test-data/mock-responses/vertexai',
66+
unit_tests_dir + 'vertexai-sdk-test-data/mock-responses',
6767
unit_tests_dir + 'Resources/**/*',
6868
]
6969
end

FirebaseAI/Tests/Unit/ChatTests.swift

Lines changed: 43 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -40,57 +40,58 @@ final class ChatTests: XCTestCase {
4040
let fileURL = try XCTUnwrap(bundle.url(
4141
forResource: "streaming-success-basic-reply-parts",
4242
withExtension: "txt",
43-
subdirectory: "vertexai"
43+
subdirectory: "mock-responses/vertexai"
4444
))
4545

4646
// Skip tests using MockURLProtocol on watchOS; unsupported in watchOS 2 and later, see
4747
// https://developer.apple.com/documentation/foundation/urlprotocol for details.
4848
#if os(watchOS)
4949
throw XCTSkip("Custom URL protocols are unsupported in watchOS 2 and later.")
50-
#endif // os(watchOS)
51-
MockURLProtocol.requestHandler = { request in
52-
let response = HTTPURLResponse(
53-
url: request.url!,
54-
statusCode: 200,
55-
httpVersion: nil,
56-
headerFields: nil
57-
)!
58-
return (response, fileURL.lines)
59-
}
50+
#else // os(watchOS)
51+
MockURLProtocol.requestHandler = { request in
52+
let response = HTTPURLResponse(
53+
url: request.url!,
54+
statusCode: 200,
55+
httpVersion: nil,
56+
headerFields: nil
57+
)!
58+
return (response, fileURL.lines)
59+
}
6060

61-
let app = FirebaseApp(instanceWithName: "testApp",
62-
options: FirebaseOptions(googleAppID: "ignore",
63-
gcmSenderID: "ignore"))
64-
let model = GenerativeModel(
65-
modelName: modelName,
66-
modelResourceName: modelResourceName,
67-
firebaseInfo: FirebaseInfo(
68-
projectID: "my-project-id",
69-
apiKey: "API_KEY",
70-
firebaseAppID: "My app ID",
71-
firebaseApp: app
72-
),
73-
apiConfig: FirebaseAI.defaultVertexAIAPIConfig,
74-
tools: nil,
75-
requestOptions: RequestOptions(),
76-
urlSession: urlSession
77-
)
78-
let chat = Chat(model: model, history: [])
79-
let input = "Test input"
80-
let stream = try chat.sendMessageStream(input)
61+
let app = FirebaseApp(instanceWithName: "testApp",
62+
options: FirebaseOptions(googleAppID: "ignore",
63+
gcmSenderID: "ignore"))
64+
let model = GenerativeModel(
65+
modelName: modelName,
66+
modelResourceName: modelResourceName,
67+
firebaseInfo: FirebaseInfo(
68+
projectID: "my-project-id",
69+
apiKey: "API_KEY",
70+
firebaseAppID: "My app ID",
71+
firebaseApp: app
72+
),
73+
apiConfig: FirebaseAI.defaultVertexAIAPIConfig,
74+
tools: nil,
75+
requestOptions: RequestOptions(),
76+
urlSession: urlSession
77+
)
78+
let chat = Chat(model: model, history: [])
79+
let input = "Test input"
80+
let stream = try chat.sendMessageStream(input)
8181

82-
// Ensure the values are parsed correctly
83-
for try await value in stream {
84-
XCTAssertNotNil(value.text)
85-
}
82+
// Ensure the values are parsed correctly
83+
for try await value in stream {
84+
XCTAssertNotNil(value.text)
85+
}
8686

87-
XCTAssertEqual(chat.history.count, 2)
88-
let part = try XCTUnwrap(chat.history[0].parts[0])
89-
let textPart = try XCTUnwrap(part as? TextPart)
90-
XCTAssertEqual(textPart.text, input)
87+
XCTAssertEqual(chat.history.count, 2)
88+
let part = try XCTUnwrap(chat.history[0].parts[0])
89+
let textPart = try XCTUnwrap(part as? TextPart)
90+
XCTAssertEqual(textPart.text, input)
9191

92-
let finalText = "1 2 3 4 5 6 7 8"
93-
let assembledExpectation = ModelContent(role: "model", parts: finalText)
94-
XCTAssertEqual(chat.history[1], assembledExpectation)
92+
let finalText = "1 2 3 4 5 6 7 8"
93+
let assembledExpectation = ModelContent(role: "model", parts: finalText)
94+
XCTAssertEqual(chat.history[1], assembledExpectation)
95+
#endif // os(watchOS)
9596
}
9697
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import FirebaseAppCheckInterop
16+
import Foundation
17+
18+
@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
19+
class AppCheckInteropFake: NSObject, AppCheckInterop {
20+
/// The placeholder token value returned when an error occurs
21+
static let placeholderTokenValue = "placeholder-token"
22+
23+
var token: String
24+
var error: Error?
25+
26+
private init(token: String, error: Error?) {
27+
self.token = token
28+
self.error = error
29+
}
30+
31+
convenience init(token: String) {
32+
self.init(token: token, error: nil)
33+
}
34+
35+
convenience init(error: Error) {
36+
self.init(token: AppCheckInteropFake.placeholderTokenValue, error: error)
37+
}
38+
39+
func getToken(forcingRefresh: Bool) async -> any FIRAppCheckTokenResultInterop {
40+
return AppCheckTokenResultInteropFake(token: token, error: error)
41+
}
42+
43+
func tokenDidChangeNotificationName() -> String {
44+
fatalError("\(#function) not implemented.")
45+
}
46+
47+
func notificationTokenKey() -> String {
48+
fatalError("\(#function) not implemented.")
49+
}
50+
51+
func notificationAppNameKey() -> String {
52+
fatalError("\(#function) not implemented.")
53+
}
54+
55+
private class AppCheckTokenResultInteropFake: NSObject, FIRAppCheckTokenResultInterop {
56+
var token: String
57+
var error: Error?
58+
59+
init(token: String, error: Error?) {
60+
self.token = token
61+
self.error = error
62+
}
63+
}
64+
}
65+
66+
struct AppCheckErrorFake: Error {}

0 commit comments

Comments
 (0)