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+ sourceCompatibility JavaVersion . VERSION_17
21+ targetCompatibility JavaVersion . VERSION_17
22+ }
23+
1524 buildFeatures {
1625 buildConfig true
1726 }
@@ -30,10 +39,15 @@ android {
3039 }
3140
3241 testOptions {
33- unitTests. all {
34- // jacoco {
35- // includeNoLocationClasses = true
36- // }
42+ unitTests {
43+ includeAndroidResources = true
44+ returnDefaultValues = true
45+ all {
46+ jacoco {
47+ includeNoLocationClasses = true
48+ excludes = [' jdk.internal.*' ]
49+ }
50+ }
3751 }
3852 }
3953 // signing {
@@ -109,18 +123,37 @@ dependencies {
109123 def multidex = " 2.0.1"
110124 def volley = " 1.2.1"
111125 def junit = " 4.13.2"
126+ def mockito = " 5.2.0"
127+ def mockitoKotlin = " 2.2.0"
112128 configurations. configureEach { resolutionStrategy. force ' com.android.support:support-annotations:23.1.0' }
113129 implementation fileTree(include : [' *.jar' ], dir : ' libs' )
114130 implementation " com.android.volley:volley:$volley "
115131 implementation " junit:junit:$junit "
116132
117133 // For AGP 7.4+
118134 coreLibraryDesugaring ' com.android.tools:desugar_jdk_libs:2.0.4'
135+
136+ // Unit Testing Dependencies
119137 testImplementation ' junit:junit:4.13.2'
138+ testImplementation " org.mockito:mockito-core:$mockito "
139+ testImplementation " org.mockito:mockito-inline:$mockito "
140+ testImplementation ' org.mockito:mockito-android:5.2.0'
141+ testImplementation ' org.robolectric:robolectric:4.15' // Updated to fix security vulnerabilities
142+ testImplementation ' androidx.test:core:1.5.0'
143+ testImplementation ' androidx.test:runner:1.5.2'
144+ testImplementation ' androidx.test.ext:junit:1.1.5'
145+ testImplementation ' com.squareup.okhttp3:mockwebserver:4.12.0'
146+ testImplementation ' org.json:json:20231013'
147+ // PowerMock for advanced mocking
148+ testImplementation ' org.powermock:powermock-module-junit4:2.0.9'
149+ testImplementation ' org.powermock:powermock-api-mockito2:2.0.9'
150+ testImplementation ' org.powermock:powermock-core:2.0.9'
151+
152+ // Android Test Dependencies
120153 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 ' , {
154+ androidTestImplementation ' androidx.test:runner:1.5.2 '
155+ androidTestImplementation ' androidx.test.ext:junit:1.1.5 '
156+ androidTestImplementation(' androidx.test.espresso:espresso-core:3.5.1 ' , {
124157 exclude group : ' com.android.support' , module : ' support-annotations'
125158 })
126159
@@ -200,22 +233,198 @@ mavenPublishing {
200233 }
201234}
202235
236+ jacoco {
237+ toolVersion = " 0.8.12"
238+ }
239+
203240tasks. register(' jacocoTestReport' , JacocoReport ) {
204- dependsOn(' testDebugUnitTest' , ' createDebugCoverageReport' )
241+ dependsOn(' testDebugUnitTest' )
242+
243+ reports {
244+ xml. required = true
245+ html. required = true
246+ csv. required = false
247+
248+ xml. outputLocation = file(" ${ buildDir} /reports/jacoco/jacocoTestReport/jacocoTestReport.xml" )
249+ html. outputLocation = file(" ${ buildDir} /reports/jacoco/jacocoTestReport/html" )
250+ }
251+
252+ def excludePatterns = [
253+ ' **/R.class' ,
254+ ' **/R$*.class' ,
255+ ' **/BuildConfig.*' ,
256+ ' **/Manifest*.*' ,
257+ ' **/*Test*.*' ,
258+ ' android/**/*.*' ,
259+ ' **/*$ViewInjector*.*' ,
260+ ' **/*$ViewBinder*.*' ,
261+ ' **/Lambda$*.class' ,
262+ ' **/Lambda.class' ,
263+ ' **/*Lambda.class' ,
264+ ' **/*Lambda*.class' ,
265+ ' **/*_MembersInjector.class' ,
266+ ' **/Dagger*Component*.*' ,
267+ ' **/*Module_*Factory.class' ,
268+ ' **/AutoValue_*.*' ,
269+ ' **/*JavascriptBridge.class' ,
270+ ' **/package-info.class' ,
271+ ' **/TestActivity.class' ,
272+ // External library exclusions
273+ ' **/okhttp/**' ,
274+ ' **/okio/**' ,
275+ ' **/txtmark/**' ,
276+ ' **/retrofit2/**' ,
277+ ' **/volley/**'
278+ ]
279+
280+ sourceDirectories. setFrom(files([
281+ " ${ project.projectDir} /src/main/java"
282+ ]))
283+
284+ classDirectories. setFrom(files([
285+ fileTree(dir : " ${ buildDir} /intermediates/javac/debug" , excludes : excludePatterns),
286+ fileTree(dir : " ${ buildDir} /tmp/kotlin-classes/debug" , excludes : excludePatterns)
287+ ]))
288+
289+ executionData. setFrom(fileTree(buildDir). include([
290+ " outputs/unit_test_code_coverage/debugUnitTest/testDebugUnitTest.exec" ,
291+ " jacoco/testDebugUnitTest.exec"
292+ ]))
293+ }
294+
295+ // Combined coverage report for both unit and instrumentation tests
296+ tasks. register(' jacocoCombinedReport' , JacocoReport ) {
297+ // This task can run after both test types complete
298+ // Make it depend on both if they're being run
299+ group = " Reporting"
300+ description = " Generate Jacoco coverage reports for both unit and instrumentation tests"
301+
205302 reports {
303+ xml. required = true
206304 html. required = true
305+ csv. required = false
306+
307+ xml. outputLocation = file(" ${ buildDir} /reports/jacoco/jacocoCombinedReport/jacocoCombinedReport.xml" )
308+ html. outputLocation = file(" ${ buildDir} /reports/jacoco/jacocoCombinedReport/html" )
207309 }
310+
311+ def excludePatterns = [
312+ ' **/R.class' ,
313+ ' **/R$*.class' ,
314+ ' **/BuildConfig.*' ,
315+ ' **/Manifest*.*' ,
316+ ' **/*Test*.*' ,
317+ ' android/**/*.*' ,
318+ ' **/*$ViewInjector*.*' ,
319+ ' **/*$ViewBinder*.*' ,
320+ ' **/Lambda$*.class' ,
321+ ' **/Lambda.class' ,
322+ ' **/*Lambda.class' ,
323+ ' **/*Lambda*.class' ,
324+ ' **/*_MembersInjector.class' ,
325+ ' **/Dagger*Component*.*' ,
326+ ' **/*Module_*Factory.class' ,
327+ ' **/AutoValue_*.*' ,
328+ ' **/*JavascriptBridge.class' ,
329+ ' **/package-info.class' ,
330+ ' **/TestActivity.class' ,
331+ // External library exclusions
332+ ' **/okhttp/**' ,
333+ ' **/okio/**' ,
334+ ' **/txtmark/**' ,
335+ ' **/retrofit2/**' ,
336+ ' **/volley/**'
337+ ]
338+
339+ sourceDirectories. setFrom(files([
340+ " ${ project.projectDir} /src/main/java"
341+ ]))
342+
343+ classDirectories. setFrom(files([
344+ fileTree(dir : " ${ buildDir} /intermediates/javac/debug" , excludes : excludePatterns),
345+ fileTree(dir : " ${ buildDir} /tmp/kotlin-classes/debug" , excludes : excludePatterns)
346+ ]))
347+
348+ // Collect execution data from both unit tests and instrumentation tests
349+ executionData. setFrom(fileTree(buildDir). include([
350+ // Unit test coverage
351+ " outputs/unit_test_code_coverage/debugUnitTest/testDebugUnitTest.exec" ,
352+ " jacoco/testDebugUnitTest.exec" ,
353+ // Instrumentation test coverage
354+ " outputs/code_coverage/debugAndroidTest/connected/**/*.ec"
355+ ]))
208356}
209357
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- }))
358+ tasks. register(' jacocoTestCoverageVerification' , JacocoCoverageVerification ) {
359+ dependsOn(' testDebugUnitTest' )
360+
361+ violationRules {
362+ rule {
363+ limit {
364+ minimum = 0.99
365+ }
366+ }
367+
368+ rule {
369+ element = ' CLASS'
370+ limit {
371+ counter = ' LINE'
372+ value = ' COVEREDRATIO'
373+ minimum = 0.99
374+ }
375+ excludes = [
376+ ' *.R' ,
377+ ' *.R$*' ,
378+ ' *.BuildConfig' ,
379+ ' *.*Test*' ,
380+ ' *.TestActivity'
381+ ]
382+ }
383+
384+ rule {
385+ element = ' CLASS'
386+ limit {
387+ counter = ' BRANCH'
388+ value = ' COVEREDRATIO'
389+ minimum = 0.99
390+ }
391+ excludes = [
392+ ' *.R' ,
393+ ' *.R$*' ,
394+ ' *.BuildConfig' ,
395+ ' *.*Test*' ,
396+ ' *.TestActivity'
397+ ]
398+ }
220399 }
400+
401+ def excludePatterns = [
402+ ' **/R.class' ,
403+ ' **/R$*.class' ,
404+ ' **/BuildConfig.*' ,
405+ ' **/Manifest*.*' ,
406+ ' **/*Test*.*' ,
407+ ' android/**/*.*' ,
408+ ' **/package-info.class' ,
409+ ' **/TestActivity.class'
410+ ]
411+
412+ sourceDirectories. setFrom(files([
413+ " ${ project.projectDir} /src/main/java"
414+ ]))
415+
416+ classDirectories. setFrom(files([
417+ fileTree(dir : " ${ buildDir} /intermediates/javac/debug" , excludes : excludePatterns),
418+ fileTree(dir : " ${ buildDir} /tmp/kotlin-classes/debug" , excludes : excludePatterns)
419+ ]))
420+
421+ executionData. setFrom(fileTree(buildDir). include([
422+ " outputs/unit_test_code_coverage/debugUnitTest/testDebugUnitTest.exec" ,
423+ " jacoco/testDebugUnitTest.exec"
424+ ]))
425+ }
426+
427+ // Make check task depend on coverage verification
428+ tasks. named(' check' ) {
429+ dependsOn(' jacocoTestReport' , ' jacocoTestCoverageVerification' )
221430}
0 commit comments