Skip to content

Commit 3d091b0

Browse files
authored
feature/increase test coverage (#157)
* test: add unit tests for models, utils, common, decryption, and sticky bucket * test: add GrowthBookSDK public API tests * test: add init(json:) tests for model types * test: add extensions and utils tests * test: add constants and model tests * fix: resolve swift concurrency mutation error in sdk tests * test: cover condition evaluator operators * test: add feature evaluator tests * test: add experiment evaluator tests * test: extend viewmodel and evaluator coverage * test: cover formatter, context and model gaps
1 parent 87a30a8 commit 3d091b0

16 files changed

Lines changed: 3847 additions & 0 deletions

GrowthBook-IOS.xcodeproj/project.pbxproj

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,21 @@
1616
84095C902818245700ADDF19 /* Formatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84095C8E2818245100ADDF19 /* Formatter.swift */; };
1717
84095C932818254400ADDF19 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84095C91281824DB00ADDF19 /* Theme.swift */; };
1818
840E386F2B88BD75003DFC9F /* ExperimentHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840E386E2B88BD75003DFC9F /* ExperimentHelper.swift */; };
19+
843315FD2FA22A1B00D1C388 /* UtilsExtendedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 843315FC2FA22A1B00D1C388 /* UtilsExtendedTests.swift */; };
20+
843315FE2FA22A1B00D1C388 /* FeatureModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 843315FA2FA22A1B00D1C388 /* FeatureModelTests.swift */; };
21+
843315FF2FA22A1B00D1C388 /* CommonTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 843315F82FA22A1B00D1C388 /* CommonTests.swift */; };
22+
843316002FA22A1B00D1C388 /* StickyBucketServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 843315FB2FA22A1B00D1C388 /* StickyBucketServiceTests.swift */; };
23+
843316012FA22A1B00D1C388 /* DecryptionUtilsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 843315F92FA22A1B00D1C388 /* DecryptionUtilsTests.swift */; };
24+
843316062FA252B300D1C388 /* GrowthBookSDKTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 843316052FA252B300D1C388 /* GrowthBookSDKTests.swift */; };
25+
843316082FA2704B00D1C388 /* FeatureModelInitJsonTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 843316072FA2704B00D1C388 /* FeatureModelInitJsonTests.swift */; };
26+
8433160C2FA2B6AD00D1C388 /* ExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8433160A2FA2B6AD00D1C388 /* ExtensionsTests.swift */; };
27+
8433160D2FA2B6AD00D1C388 /* UtilsStickyBucketTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8433160B2FA2B6AD00D1C388 /* UtilsStickyBucketTests.swift */; };
28+
8433160E2FA2B6AD00D1C388 /* ConstantsAndModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 843316092FA2B6AD00D1C388 /* ConstantsAndModelTests.swift */; };
29+
843316102FA3F3AE00D1C388 /* ConditionEvaluatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8433160F2FA3F3AE00D1C388 /* ConditionEvaluatorTests.swift */; };
30+
843316122FA3F76100D1C388 /* FeatureEvaluatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 843316112FA3F76100D1C388 /* FeatureEvaluatorTests.swift */; };
31+
843316142FA3F9B900D1C388 /* ExperimentEvaluatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 843316132FA3F9B900D1C388 /* ExperimentEvaluatorTests.swift */; };
32+
843316162FA3FAEF00D1C388 /* FeaturesViewModelExtendedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 843316152FA3FAEF00D1C388 /* FeaturesViewModelExtendedTests.swift */; };
33+
843316182FA3FD1800D1C388 /* MiscCoverageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 843316172FA3FD1800D1C388 /* MiscCoverageTests.swift */; };
1934
844838362E054AD200784A36 /* NetworkRetryHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844838342E054AD200784A36 /* NetworkRetryHandler.swift */; };
2035
846A7A562D6CAE56007CCFD1 /* GlobalContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846A7A552D6CAE53007CCFD1 /* GlobalContext.swift */; };
2136
8483E6FA2ABC60B900306B55 /* DecryptionUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8483E6F92ABC60B900306B55 /* DecryptionUtils.swift */; };
@@ -94,6 +109,21 @@
94109
84095C8E2818245100ADDF19 /* Formatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Formatter.swift; sourceTree = "<group>"; };
95110
84095C91281824DB00ADDF19 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = "<group>"; };
96111
840E386E2B88BD75003DFC9F /* ExperimentHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExperimentHelper.swift; sourceTree = "<group>"; };
112+
843315F82FA22A1B00D1C388 /* CommonTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonTests.swift; sourceTree = "<group>"; };
113+
843315F92FA22A1B00D1C388 /* DecryptionUtilsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecryptionUtilsTests.swift; sourceTree = "<group>"; };
114+
843315FA2FA22A1B00D1C388 /* FeatureModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureModelTests.swift; sourceTree = "<group>"; };
115+
843315FB2FA22A1B00D1C388 /* StickyBucketServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickyBucketServiceTests.swift; sourceTree = "<group>"; };
116+
843315FC2FA22A1B00D1C388 /* UtilsExtendedTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UtilsExtendedTests.swift; sourceTree = "<group>"; };
117+
843316052FA252B300D1C388 /* GrowthBookSDKTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GrowthBookSDKTests.swift; sourceTree = "<group>"; };
118+
843316072FA2704B00D1C388 /* FeatureModelInitJsonTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureModelInitJsonTests.swift; sourceTree = "<group>"; };
119+
843316092FA2B6AD00D1C388 /* ConstantsAndModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsAndModelTests.swift; sourceTree = "<group>"; };
120+
8433160A2FA2B6AD00D1C388 /* ExtensionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionsTests.swift; sourceTree = "<group>"; };
121+
8433160B2FA2B6AD00D1C388 /* UtilsStickyBucketTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UtilsStickyBucketTests.swift; sourceTree = "<group>"; };
122+
8433160F2FA3F3AE00D1C388 /* ConditionEvaluatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConditionEvaluatorTests.swift; sourceTree = "<group>"; };
123+
843316112FA3F76100D1C388 /* FeatureEvaluatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureEvaluatorTests.swift; sourceTree = "<group>"; };
124+
843316132FA3F9B900D1C388 /* ExperimentEvaluatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExperimentEvaluatorTests.swift; sourceTree = "<group>"; };
125+
843316152FA3FAEF00D1C388 /* FeaturesViewModelExtendedTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeaturesViewModelExtendedTests.swift; sourceTree = "<group>"; };
126+
843316172FA3FD1800D1C388 /* MiscCoverageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MiscCoverageTests.swift; sourceTree = "<group>"; };
97127
843363F627F845EB0072BFDC /* GrowthBookSDK.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GrowthBookSDK.swift; sourceTree = "<group>"; };
98128
843363F827F845EB0072BFDC /* NetworkClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkClient.swift; sourceTree = "<group>"; };
99129
843363FA27F845EB0072BFDC /* FeaturesDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeaturesDataModel.swift; sourceTree = "<group>"; };
@@ -288,6 +318,21 @@
288318
849114BE280DA71E00C0B9A5 /* GrowthBookTests */ = {
289319
isa = PBXGroup;
290320
children = (
321+
843316172FA3FD1800D1C388 /* MiscCoverageTests.swift */,
322+
843316152FA3FAEF00D1C388 /* FeaturesViewModelExtendedTests.swift */,
323+
843316132FA3F9B900D1C388 /* ExperimentEvaluatorTests.swift */,
324+
843316112FA3F76100D1C388 /* FeatureEvaluatorTests.swift */,
325+
8433160F2FA3F3AE00D1C388 /* ConditionEvaluatorTests.swift */,
326+
843316092FA2B6AD00D1C388 /* ConstantsAndModelTests.swift */,
327+
8433160A2FA2B6AD00D1C388 /* ExtensionsTests.swift */,
328+
8433160B2FA2B6AD00D1C388 /* UtilsStickyBucketTests.swift */,
329+
843316072FA2704B00D1C388 /* FeatureModelInitJsonTests.swift */,
330+
843316052FA252B300D1C388 /* GrowthBookSDKTests.swift */,
331+
843315F82FA22A1B00D1C388 /* CommonTests.swift */,
332+
843315F92FA22A1B00D1C388 /* DecryptionUtilsTests.swift */,
333+
843315FA2FA22A1B00D1C388 /* FeatureModelTests.swift */,
334+
843315FB2FA22A1B00D1C388 /* StickyBucketServiceTests.swift */,
335+
843315FC2FA22A1B00D1C388 /* UtilsExtendedTests.swift */,
291336
5D5EAD1E2EF1DED60049F432 /* LruETagCacheTests.swift */,
292337
84391F2528016C5C003309DC /* Source */,
293338
84391F0827FD64EB003309DC /* FeaturesViewModelTests.swift */,
@@ -465,14 +510,29 @@
465510
files = (
466511
849114C7280DA73000C0B9A5 /* FeaturesViewModelTests.swift in Sources */,
467512
5D5EAD1F2EF1DED60049F432 /* LruETagCacheTests.swift in Sources */,
513+
843316162FA3FAEF00D1C388 /* FeaturesViewModelExtendedTests.swift in Sources */,
514+
843316062FA252B300D1C388 /* GrowthBookSDKTests.swift in Sources */,
468515
849114C8280DA73000C0B9A5 /* ConditionTests.swift in Sources */,
469516
849114C9280DA73000C0B9A5 /* ExperimentRunTests.swift in Sources */,
470517
849114CA280DA73100C0B9A5 /* FeatureValueTests.swift in Sources */,
471518
848B8044294B5CB900B22168 /* CryptoTests.swift in Sources */,
519+
843315FD2FA22A1B00D1C388 /* UtilsExtendedTests.swift in Sources */,
520+
843316182FA3FD1800D1C388 /* MiscCoverageTests.swift in Sources */,
521+
843316122FA3F76100D1C388 /* FeatureEvaluatorTests.swift in Sources */,
522+
843315FE2FA22A1B00D1C388 /* FeatureModelTests.swift in Sources */,
523+
843315FF2FA22A1B00D1C388 /* CommonTests.swift in Sources */,
524+
8433160C2FA2B6AD00D1C388 /* ExtensionsTests.swift in Sources */,
525+
8433160D2FA2B6AD00D1C388 /* UtilsStickyBucketTests.swift in Sources */,
526+
8433160E2FA2B6AD00D1C388 /* ConstantsAndModelTests.swift in Sources */,
527+
843316002FA22A1B00D1C388 /* StickyBucketServiceTests.swift in Sources */,
528+
843316012FA22A1B00D1C388 /* DecryptionUtilsTests.swift in Sources */,
529+
843316082FA2704B00D1C388 /* FeatureModelInitJsonTests.swift in Sources */,
472530
849114CB280DA73100C0B9A5 /* TestHelper.swift in Sources */,
473531
849114CC280DA73100C0B9A5 /* UtilsTests.swift in Sources */,
474532
84E82C3D2BD2AF8F003F000B /* StickyBucketingTests.swift in Sources */,
533+
843316142FA3F9B900D1C388 /* ExperimentEvaluatorTests.swift in Sources */,
475534
849114CD280DA73100C0B9A5 /* GrowthBookSDKBuilderTests.swift in Sources */,
535+
843316102FA3F3AE00D1C388 /* ConditionEvaluatorTests.swift in Sources */,
476536
849114CE280DA73100C0B9A5 /* MockNetworkClient.swift in Sources */,
477537
849114CF280DA73100C0B9A5 /* CachingManagerTest.swift in Sources */,
478538
);

GrowthBookTests/CommonTests.swift

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import XCTest
2+
@testable import GrowthBook
3+
4+
class CommonTests: XCTestCase {
5+
6+
// MARK: - fnv1 / fnv1a
7+
8+
func testFnv1aProducesKnownHash() {
9+
// FNV-1a of empty string with 32-bit basis
10+
let result = Common.fnv1a([UInt8](), offsetBasis: Common.offsetBasis32, prime: Common.prime32)
11+
XCTAssertEqual(result, Common.offsetBasis32, "FNV-1a of empty input equals the offset basis")
12+
}
13+
14+
func testFnv1ProducesKnownHash() {
15+
let result = Common.fnv1([UInt8](), offsetBasis: Common.offsetBasis32, prime: Common.prime32)
16+
XCTAssertEqual(result, Common.offsetBasis32)
17+
}
18+
19+
func testFnv1aIsDifferentFromFnv1ForSameInput() {
20+
let bytes: [UInt8] = Array("hello".utf8)
21+
let fnv1result = Common.fnv1(bytes, offsetBasis: Common.offsetBasis32, prime: Common.prime32)
22+
let fnv1aResult = Common.fnv1a(bytes, offsetBasis: Common.offsetBasis32, prime: Common.prime32)
23+
XCTAssertNotEqual(fnv1result, fnv1aResult)
24+
}
25+
26+
func testFnv1aIsDeterministic() {
27+
let bytes: [UInt8] = Array("deterministic".utf8)
28+
let first = Common.fnv1a(bytes, offsetBasis: Common.offsetBasis32, prime: Common.prime32)
29+
let second = Common.fnv1a(bytes, offsetBasis: Common.offsetBasis32, prime: Common.prime32)
30+
XCTAssertEqual(first, second)
31+
}
32+
33+
func testFnv1a64BitProducesLargerValue() {
34+
let bytes: [UInt8] = Array("test".utf8)
35+
let result64: UInt64 = Common.fnv1a(bytes, offsetBasis: Common.offsetBasis64, prime: Common.prime64)
36+
XCTAssertGreaterThan(result64, 0)
37+
}
38+
39+
// MARK: - isEqual
40+
41+
func testIsEqualStrings() {
42+
XCTAssertTrue(Common.isEqual("hello", "hello"))
43+
XCTAssertFalse(Common.isEqual("hello", "world"))
44+
}
45+
46+
func testIsEqualInts() {
47+
XCTAssertTrue(Common.isEqual(42, 42))
48+
XCTAssertFalse(Common.isEqual(1, 2))
49+
}
50+
51+
// MARK: - isIn (case-sensitive)
52+
53+
func testIsInStringFound() {
54+
XCTAssertTrue(Common.isIn(actual: "b", expected: ["a", "b", "c"]))
55+
}
56+
57+
func testIsInStringNotFound() {
58+
XCTAssertFalse(Common.isIn(actual: "z", expected: ["a", "b", "c"]))
59+
}
60+
61+
func testIsInIntFound() {
62+
XCTAssertTrue(Common.isIn(actual: 2, expected: [1, 2, 3]))
63+
}
64+
65+
func testIsInIntNotFound() {
66+
XCTAssertFalse(Common.isIn(actual: 99, expected: [1, 2, 3]))
67+
}
68+
69+
func testIsInArrayActualOverlapsExpected() {
70+
XCTAssertTrue(Common.isIn(actual: ["x", "b"], expected: ["a", "b", "c"]))
71+
}
72+
73+
func testIsInArrayActualNoOverlap() {
74+
XCTAssertFalse(Common.isIn(actual: ["x", "y"], expected: ["a", "b", "c"]))
75+
}
76+
77+
func testIsInJSONStringFound() {
78+
let jsonExpected: [JSON] = [JSON("alpha"), JSON("beta"), JSON("gamma")]
79+
XCTAssertTrue(Common.isIn(actual: JSON("beta"), expected: jsonExpected))
80+
}
81+
82+
func testIsInJSONStringNotFound() {
83+
let jsonExpected: [JSON] = [JSON("alpha"), JSON("beta")]
84+
XCTAssertFalse(Common.isIn(actual: JSON("delta"), expected: jsonExpected))
85+
}
86+
87+
func testIsInJSONArrayActualOverlaps() {
88+
let jsonExpected: [JSON] = [JSON("a"), JSON("b"), JSON("c")]
89+
let actual = JSON(["b", "x"])
90+
XCTAssertTrue(Common.isIn(actual: actual, expected: jsonExpected))
91+
}
92+
93+
// MARK: - isIn (case-insensitive)
94+
95+
func testIsInCaseInsensitiveStringMatch() {
96+
let expected: [JSON] = [JSON("hello"), JSON("world")]
97+
XCTAssertTrue(Common.isIn(actual: JSON("HELLO"), expected: expected, insensitive: true))
98+
}
99+
100+
func testIsInCaseInsensitiveStringNoMatch() {
101+
let expected: [JSON] = [JSON("hello"), JSON("world")]
102+
XCTAssertFalse(Common.isIn(actual: JSON("foo"), expected: expected, insensitive: true))
103+
}
104+
105+
func testIsInCaseInsensitivePlainStringActual() {
106+
let expected: [JSON] = [JSON("Apple"), JSON("Banana")]
107+
XCTAssertTrue(Common.isIn(actual: "APPLE", expected: expected, insensitive: true))
108+
}
109+
110+
func testIsInCaseInsensitiveJSONArrayActual() {
111+
let expected: [JSON] = [JSON("Cat"), JSON("Dog")]
112+
let actual = JSON(["cat", "fish"])
113+
XCTAssertTrue(Common.isIn(actual: actual, expected: expected, insensitive: true))
114+
}
115+
116+
func testIsInCaseInsensitiveJSONArrayNoMatch() {
117+
let expected: [JSON] = [JSON("Cat"), JSON("Dog")]
118+
let actual = JSON(["fish", "bird"])
119+
XCTAssertFalse(Common.isIn(actual: actual, expected: expected, insensitive: true))
120+
}
121+
122+
// MARK: - isInAll
123+
124+
func testIsInAllEveryExpectedItemMatchesSomething() {
125+
let actual = JSON(["a", "b", "c"])
126+
let expected: [JSON] = [JSON("a"), JSON("b")]
127+
let result = Common.isInAll(actual: actual, expected: expected, savedGroups: nil, insensitive: false) { _, _, _ in true }
128+
XCTAssertTrue(result)
129+
}
130+
131+
func testIsInAllReturnsFalseWhenOneExpectedItemHasNoMatch() {
132+
let actual = JSON(["a"])
133+
let expected: [JSON] = [JSON("a"), JSON("b")]
134+
let result = Common.isInAll(actual: actual, expected: expected, savedGroups: nil, insensitive: false) { expectedItem, actualItem, _ in
135+
expectedItem == actualItem
136+
}
137+
XCTAssertFalse(result)
138+
}
139+
140+
func testIsInAllReturnsFalseForNonArray() {
141+
let actual = JSON("not-an-array")
142+
let expected: [JSON] = [JSON("anything")]
143+
let result = Common.isInAll(actual: actual, expected: expected, savedGroups: nil, insensitive: false) { _, _, _ in true }
144+
XCTAssertFalse(result)
145+
}
146+
147+
func testIsInAllReturnsTrueForEmptyExpected() {
148+
let actual = JSON(["a", "b"])
149+
let result = Common.isInAll(actual: actual, expected: [], savedGroups: nil, insensitive: false) { _, _, _ in false }
150+
XCTAssertTrue(result)
151+
}
152+
}

0 commit comments

Comments
 (0)