11plugins {
22 id " com.android.library"
33 id " com.vanniktech.maven.publish" version " 0.33.0"
4+ id ' jacoco'
45}
56
67ext {
1213android {
1314 namespace " com.contentstack.sdk"
1415 compileSdk 34 // Using latest stable Android SDK version
16+
17+ // SDK compiles to Java 17 for JaCoCo compatibility
18+ // But can be built with Java 21 - tests use Java 17 toolchain
19+ compileOptions {
20+ coreLibraryDesugaringEnabled true
21+ sourceCompatibility JavaVersion . VERSION_17
22+ targetCompatibility JavaVersion . VERSION_17
23+ }
24+
1525 buildFeatures {
1626 buildConfig true
1727 }
@@ -30,10 +40,15 @@ android {
3040 }
3141
3242 testOptions {
33- unitTests. all {
34- // jacoco {
35- // includeNoLocationClasses = true
36- // }
43+ unitTests {
44+ includeAndroidResources = true
45+ returnDefaultValues = true
46+ all {
47+ jacoco {
48+ includeNoLocationClasses = true
49+ excludes = [' jdk.internal.*' ]
50+ }
51+ }
3752 }
3853 }
3954 // signing {
@@ -109,18 +124,37 @@ dependencies {
109124 def multidex = " 2.0.1"
110125 def volley = " 1.2.1"
111126 def junit = " 4.13.2"
127+ def mockito = " 5.2.0"
128+ def mockitoKotlin = " 2.2.0"
112129 configurations. configureEach { resolutionStrategy. force ' com.android.support:support-annotations:23.1.0' }
113130 implementation fileTree(include : [' *.jar' ], dir : ' libs' )
114131 implementation " com.android.volley:volley:$volley "
115132 implementation " junit:junit:$junit "
116133
117134 // For AGP 7.4+
118135 coreLibraryDesugaring ' com.android.tools:desugar_jdk_libs:2.0.4'
136+
137+ // Unit Testing Dependencies
119138 testImplementation ' junit:junit:4.13.2'
139+ testImplementation " org.mockito:mockito-core:$mockito "
140+ testImplementation " org.mockito:mockito-inline:$mockito "
141+ testImplementation ' org.mockito:mockito-android:5.2.0'
142+ testImplementation ' org.robolectric:robolectric:4.15' // Updated to fix security vulnerabilities
143+ testImplementation ' androidx.test:core:1.5.0'
144+ testImplementation ' androidx.test:runner:1.5.2'
145+ testImplementation ' androidx.test.ext:junit:1.1.5'
146+ testImplementation ' com.squareup.okhttp3:mockwebserver:4.12.0'
147+ testImplementation ' org.json:json:20231013'
148+ // PowerMock for advanced mocking
149+ testImplementation ' org.powermock:powermock-module-junit4:2.0.9'
150+ testImplementation ' org.powermock:powermock-api-mockito2:2.0.9'
151+ testImplementation ' org.powermock:powermock-core:2.0.9'
152+
153+ // Android Test Dependencies
120154 androidTestImplementation ' androidx.test:core:1.5.0'
121- testImplementation ' org.robolectric:robolectric:4.6.1 '
122-
123- androidTestImplementation(' androidx.test.espresso:espresso-core:3.1.0 ' , {
155+ androidTestImplementation ' androidx.test:runner:1.5.2 '
156+ androidTestImplementation ' androidx.test.ext:junit:1.1.5 '
157+ androidTestImplementation(' androidx.test.espresso:espresso-core:3.5.1 ' , {
124158 exclude group : ' com.android.support' , module : ' support-annotations'
125159 })
126160
@@ -200,22 +234,170 @@ mavenPublishing {
200234 }
201235}
202236
237+ jacoco {
238+ toolVersion = " 0.8.12"
239+ }
240+
203241tasks. register(' jacocoTestReport' , JacocoReport ) {
204- dependsOn(' testDebugUnitTest' , ' createDebugCoverageReport' )
242+ dependsOn(' testDebugUnitTest' )
243+
205244 reports {
245+ xml. required = true
206246 html. required = true
247+ csv. required = false
248+
249+ xml. outputLocation = file(" ${ buildDir} /reports/jacoco/jacocoTestReport/jacocoTestReport.xml" )
250+ html. outputLocation = file(" ${ buildDir} /reports/jacoco/jacocoTestReport/html" )
207251 }
252+
253+ def excludePatterns = [
254+ ' **/R.class' ,
255+ ' **/R$*.class' ,
256+ ' **/BuildConfig.*' ,
257+ ' **/Manifest*.*' ,
258+ ' **/*Test*.*' ,
259+ ' android/**/*.*' ,
260+ ' **/*$ViewInjector*.*' ,
261+ ' **/*$ViewBinder*.*' ,
262+ ' **/Lambda$*.class' ,
263+ ' **/Lambda.class' ,
264+ ' **/*Lambda.class' ,
265+ ' **/*Lambda*.class' ,
266+ ' **/*_MembersInjector.class' ,
267+ ' **/Dagger*Component*.*' ,
268+ ' **/*Module_*Factory.class' ,
269+ ' **/AutoValue_*.*' ,
270+ ' **/*JavascriptBridge.class' ,
271+ ' **/package-info.class' ,
272+ ' **/TestActivity.class' ,
273+ // External library exclusions
274+ ' **/okhttp/**' ,
275+ ' **/okio/**' ,
276+ ' **/txtmark/**' ,
277+ ' **/retrofit2/**' ,
278+ ' **/volley/**' ,
279+ ' **/CSConnectionRequest.class' ,
280+ // Exclude callback interfaces and their anonymous implementations
281+ ' **/SyncResultCallBack.class' ,
282+ ' **/Stack$*.class'
283+ ]
284+
285+ sourceDirectories. setFrom(files([
286+ " ${ project.projectDir} /src/main/java"
287+ ]))
288+
289+ classDirectories. setFrom(files([
290+ fileTree(dir : " ${ buildDir} /intermediates/javac/debug" , excludes : excludePatterns),
291+ fileTree(dir : " ${ buildDir} /tmp/kotlin-classes/debug" , excludes : excludePatterns)
292+ ]))
293+
294+ executionData. setFrom(fileTree(buildDir). include([
295+ " outputs/unit_test_code_coverage/debugUnitTest/testDebugUnitTest.exec" ,
296+ " jacoco/testDebugUnitTest.exec"
297+ ]))
208298}
209299
210- // Configure jacocoTestReport after evaluation when classDirectories is available
211- project. afterEvaluate {
212- tasks. named(' jacocoTestReport' , JacocoReport ) {
213- classDirectories. setFrom(files(classDirectories. files. collect {
214- fileTree(dir : it, exclude : [
215- ' **com/contentstack/okhttp**' ,
216- ' **com/contentstack/okio**' ,
217- ' **com/contentstack/txtmark**'
218- ])
219- }))
300+ // Combined coverage report for both unit and instrumentation tests
301+ tasks. register(' jacocoCombinedReport' , JacocoReport ) {
302+ // This task can run after both test types complete
303+ // Make it depend on both if they're being run
304+ group = " Reporting"
305+ description = " Generate Jacoco coverage reports for both unit and instrumentation tests"
306+
307+ reports {
308+ xml. required = true
309+ html. required = true
310+ csv. required = false
311+
312+ xml. outputLocation = file(" ${ buildDir} /reports/jacoco/jacocoCombinedReport/jacocoCombinedReport.xml" )
313+ html. outputLocation = file(" ${ buildDir} /reports/jacoco/jacocoCombinedReport/html" )
220314 }
315+
316+ def excludePatterns = [
317+ ' **/R.class' ,
318+ ' **/R$*.class' ,
319+ ' **/BuildConfig.*' ,
320+ ' **/Manifest*.*' ,
321+ ' **/*Test*.*' ,
322+ ' android/**/*.*' ,
323+ ' **/*$ViewInjector*.*' ,
324+ ' **/*$ViewBinder*.*' ,
325+ ' **/Lambda$*.class' ,
326+ ' **/Lambda.class' ,
327+ ' **/*Lambda.class' ,
328+ ' **/*Lambda*.class' ,
329+ ' **/*_MembersInjector.class' ,
330+ ' **/Dagger*Component*.*' ,
331+ ' **/*Module_*Factory.class' ,
332+ ' **/AutoValue_*.*' ,
333+ ' **/*JavascriptBridge.class' ,
334+ ' **/package-info.class' ,
335+ ' **/TestActivity.class' ,
336+ // External library exclusions
337+ ' **/okhttp/**' ,
338+ ' **/okio/**' ,
339+ ' **/txtmark/**' ,
340+ ' **/retrofit2/**' ,
341+ ' **/volley/**' ,
342+ ' **/CSConnectionRequest.class' ,
343+ // Exclude callback interfaces and their anonymous implementations
344+ ' **/SyncResultCallBack.class' ,
345+ ' **/Stack$*.class'
346+ ]
347+
348+ sourceDirectories. setFrom(files([
349+ " ${ project.projectDir} /src/main/java"
350+ ]))
351+
352+ classDirectories. setFrom(files([
353+ fileTree(dir : " ${ buildDir} /intermediates/javac/debug" , excludes : excludePatterns),
354+ fileTree(dir : " ${ buildDir} /tmp/kotlin-classes/debug" , excludes : excludePatterns)
355+ ]))
356+
357+ // Collect execution data from both unit tests and instrumentation tests
358+ executionData. setFrom(fileTree(buildDir). include([
359+ // Unit test coverage
360+ " outputs/unit_test_code_coverage/debugUnitTest/testDebugUnitTest.exec" ,
361+ " jacoco/testDebugUnitTest.exec" ,
362+ // Instrumentation test coverage
363+ " outputs/code_coverage/debugAndroidTest/connected/**/*.ec"
364+ ]))
365+ }
366+
367+ tasks. register(' jacocoTestCoverageVerification' , JacocoCoverageVerification ) {
368+ dependsOn(' testDebugUnitTest' )
369+
370+ def excludePatterns = [
371+ ' **/R.class' ,
372+ ' **/R$*.class' ,
373+ ' **/BuildConfig.*' ,
374+ ' **/Manifest*.*' ,
375+ ' **/*Test*.*' ,
376+ ' android/**/*.*' ,
377+ ' **/package-info.class' ,
378+ ' **/TestActivity.class' ,
379+ ' **/CSConnectionRequest.class' ,
380+ // Exclude callback interfaces and their anonymous implementations
381+ ' **/SyncResultCallBack.class' ,
382+ ' **/Stack$*.class'
383+ ]
384+
385+ sourceDirectories. setFrom(files([
386+ " ${ project.projectDir} /src/main/java"
387+ ]))
388+
389+ classDirectories. setFrom(files([
390+ fileTree(dir : " ${ buildDir} /intermediates/javac/debug" , excludes : excludePatterns),
391+ fileTree(dir : " ${ buildDir} /tmp/kotlin-classes/debug" , excludes : excludePatterns)
392+ ]))
393+
394+ executionData. setFrom(fileTree(buildDir). include([
395+ " outputs/unit_test_code_coverage/debugUnitTest/testDebugUnitTest.exec" ,
396+ " jacoco/testDebugUnitTest.exec"
397+ ]))
398+ }
399+
400+ // Make check task depend on coverage verification
401+ tasks. named(' check' ) {
402+ dependsOn(' jacocoTestReport' , ' jacocoTestCoverageVerification' )
221403}
0 commit comments