I'm trying to add Spek testing framework to my Android Studio project. Following the instructions Here, I ended up adding the following to my module build.gradle:
testCompile 'org.jetbrains.spek:spek-api:1.1.5'
testCompile 'junit:junit:4.12'
testCompile "org.junit.platform:junit-platform-runner:1.0.0"
testRuntimeOnly 'org.jetbrains.spek:spek-junit-platform-engine:1.1.5'
Then I annotated my test with #RunWith(JUnitPlatform::class)
However, when I try to run the test, I get:
org.junit.platform.commons.util.PreconditionViolationException: Cannot create Launcher without at least one TestEngine; consider adding an engine implementation JAR to the classpath
Any idea what am I missing?
(For future references)
To use Kotlin and Spek + JUnit5 in Android Studio you need the following:
In project's build.gradle you need to have:
buildscript {
ext.kotlin_version = '1.2.10'
ext.JUnit5_version = '1.0.30'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "de.mannodermaus.gradle.plugins:android-junit5:$JUnit5_version"
}
}
In module's build.gradle you need to have:
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: "de.mannodermaus.android-junit5"
android {
...
sourceSets {
test.java.srcDirs += 'src/test/kotlin'
androidTest.java.srcDirs += 'src/androidTest/kotlin'
}
}
project.ext {
spekVersion = "1.1.5"
}
dependencies {
...
//
// TESTS
testImplementation("org.jetbrains.spek:spek-api:$spekVersion") {
exclude group: "org.jetbrains.kotlin"
}
testImplementation("org.jetbrains.spek:spek-junit-platform-engine:$spekVersion") {
exclude group: "org.junit.platform"
exclude group: "org.jetbrains.kotlin"
}
testImplementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
testImplementation junit5.unitTests()
// see https://github.com/mannodermaus/android-junit5#android-studio-workarounds
testCompileOnly junit5.unitTestsRuntime()
}
Simple Spek test
class ExampleSpekTest : Spek({
val x = 2
val y = 3
given("x = $x and y = $y") {
val sum = x + y
it("should be that x + y = 5") {
Assert.assertEquals(5, sum)
}
it("should be that x - y = -1") {
val subtract = x - y
Assert.assertEquals(-1, subtract)
}
}
})
See
- official documentation http://spekframework.org/
- official plugin for running specs from the IDE https://github.com/raniejade/spek-idea-plugin
- Tests using Spek Framework - BDD Style vs JUnit Style https://www.youtube.com/watch?v=asDZ_7ZUiX4
- Simple Android configuration of Spek and JUnit5 https://gist.github.com/Mugurell/088daf42a4d60240ba6993681e0537a5
Apparently it doesn't work well... I ended up using kotlintest which is much easier to integrate
Related
Based on answer in this link, created a test suite with Test classes
#RunWith(Suite::class)
#Suite.SuiteClasses(
DTOtoEntityMapperTest::class,
PostDataSourceCoroutinesTest::class,
PostDataSourceRxJava3Test::class
)
class JUnit5TestSuite
Returns error
org.junit.runners.model.InvalidTestClassError: Invalid test class 'com.x.DTOtoEntityMapperTest':
1. No runnable methods
But every test class added has test methods and runs individually for instance
class DTOtoEntityMapperTest {
private val postDTOList by lazy {
convertFromJsonToObjectList<PostDTO>(getResourceAsText(RESPONSE_JSON_PATH))!!
}
private val postEntityList by lazy {
convertFromJsonToObjectList<PostEntity>(getResourceAsText(RESPONSE_JSON_PATH))!!
}
#Test
fun `given PostDTO is input, should return PostEntity`() {
val mapper = DTOtoEntityMapper()
// GIVEN
val expected = postEntityList
// WHEN
val actual = mapper.map(postDTOList)
// THEN
Truth.assertThat(actual).containsExactlyElementsIn(expected)
}
}
You can now run Suites purely with jUnit 5 engines:
import org.junit.platform.suite.api.SelectClasses
import org.junit.platform.suite.api.Suite
#Suite
#SelectClasses(BasicMockKUnitTest::class, HierarchicalMockKUnitTest::class)
#SelectPackages("exercise")
class TestAllSelectPackage {
}
You'll have to import the junit-platform-suite-engine:
https://search.maven.org/artifact/org.junit.platform/junit-platform-suite-engine
There's some docs here:
https://junit.org/junit5/docs/snapshot/user-guide/#launcher-api-engines-custom
https://junit.org/junit5/docs/snapshot/api/org.junit.platform.suite.engine/org/junit/platform/suite/engine/package-summary.html
Here's an official example:
https://github.com/junit-team/junit5/blob/main/documentation/src/test/java/example/SuiteDemo.java
For JUnit 5 I used JUnitPlatform and SelectClasses (or SelectPackages):
#RunWith(JUnitPlatform::class)
#SelectClasses(Some1Test::class, Some2Test::class)
class SuiteTest
After checking for a few days, I found correctly libs to use Junit 5 test suite with kotlin
In build.gradle, add 2 libs as below:
testImplementation ("org.junit.platform:junit-platform-suite-api:1.7.0")
testImplementation ("org.junit.platform:junit-platform-runner:1.2.0")
Then you can use test suite as below:
import org.junit.platform.runner.JUnitPlatform
import org.junit.platform.suite.api.SelectClasses
import org.junit.platform.suite.api.SelectPackages
import org.junit.runner.RunWith
#RunWith(JUnitPlatform::class)
#SelectClasses(BasicMockKUnitTest::class, HierarchicalMockKUnitTest::class)
#SelectPackages("exercise")
class TestAllSelectPackage {
}
When I try to run my Junit tests (Wrote in Kotlin) I get the following exception :
java.lang.IllegalArgumentException: Attempted to find dependent attachment for class javax/xml/bind/DatatypeConverter, but could not find a suitable candidate.
I tried to comment/decomment some lines in following code, it seems that the exception occurs when I call command()
class IRIssueTests {
class DummyCommand : TypeOnlyCommandData()
private val ledgerServices = MockServices(listOf("com.my.package.name"))
private val ALICE = TestIdentity(CordaX500Name(organisation = "Alice", locality = "TestLand", country = "US"))
#Test
fun mustIncludeIssueCommand() {
val ir = IRState(
UniqueIdentifier(),
mutableListOf(ALICE.party)
)
ledgerServices.ledger {
transaction {
output(IRContract.ID, ir)
command(listOf(ALICE.publicKey), DummyCommand())
fails()
}
transaction {
output(IRContract.ID, ir)
command(listOf(ALICE.publicKey), IRContract.Commands.Issue())
verifies()
}
}
}
}
I would like to understand why I'm getting this exception and how to resolve it to make my test passing
Kotlin tests must be run with JDK8.
Change configuration of JDK8 in your project before running tests.
This will avoid this exception
trying to run android tests in android studio 0.8.1
I can run assembleDebug and assembleTest properly. But when I try to run the android test it calles assembleDebug and assembleDebugTest and with the latter I get the problem with 'Multiple dex files define'
a few pictures:
and the build.gradle of the project
dependencies {
compile project(':libraries:someLib')
compile ('com.google.android.gms:play-services:5.+')
compile ('fr.avianey:facebook-android-api:+#aar')
compile ('com.fasterxml.jackson.core:jackson-databind:2.3.1')
compile ('com.fasterxml.jackson.core:jackson-core:2.3.1')
compile ('com.fasterxml.jackson.core:jackson-annotations:2.3.0')
compile fileTree(dir: 'libs', include: '*.jar')
//androidTestCompile 'junit:junit:4.10'
//androidTestCompile 'org.robolectric:robolectric:2.1.+'
//androidTestCompile 'com.squareup:fest-android:1.0.+'
//androidTestCompile 'org.powermock:powermock-api-mockito:1.5.1'
}
and the build.gradle of the "someLib"
dependencies {
compile ('com.android.support:appcompat-v7:19.1.+')
compile ('com.nineoldandroids:library:2.4.0')
compile (group: 'com.google.guava', name: 'guava', version: '16.0-rc1')
}
they both share the rest
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.12.+'
}
}
apply plugin: 'com.android.library'
repositories {
mavenCentral()
}
android {
compileSdkVersion 19
buildToolsVersion '19.1.0'
defaultConfig {
minSdkVersion 9
targetSdkVersion 19
}
lintOptions {
abortOnError false
}
}
So far I tried 2 solutions that seems to work but I am pretty sure there has to be something better.
Anyway 1 solution:
tasks.whenTaskAdded { theTask ->
if("assembleDebugTest".toString().equals(theTask.name.toString())) {
def yourTaskName = "cleanLibs"
project.task(yourTaskName) << {
println "${project.buildDir}/intermediates/pre-dexed/test/debug/"
delete fileTree(dir: ("${project.buildDir}/intermediates/pre-dexed/test/debug/"))
}
theTask.dependsOn(yourTaskName)
def processTask = "preDexDebugTest"
project.(yourTaskName.toString()).dependsOn(processTask)
project.(processTask.toString()).dependsOn("compileDebugTestJava")
}
}
this removes the libs before they are created again, it is slow and ungly, let the libs be created, removes them and creates them again
solution 2:
tasks.whenTaskAdded { theTask ->
if("assembleDebugTest".toString().equals(theTask.name.toString())) {
def processTask = "preDexDebugTest"
project.(processTask.toString()).enabled = false
}
}
just skip the task that create the first libs, this is way better than the first one
then for both you have to extend
android {
packagingOptions {
exclude 'META-INF/DEPENDENCIES'
exclude 'META-INF/DEPENDENCIES.txt'
exclude 'META-INF/LICENSE'
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/NOTICE'
exclude 'META-INF/NOTICE.txt'
exclude 'com/flurry/org/codehaus/jackson/map/VERSION.txt'
exclude 'com/flurry/org/codehaus/jackson/impl/VERSION.txt'
exclude 'com/flurry/org/apache/avro/data/Json.avsc'
exclude 'META-INF/ASL2.0'
}
}
by whatever is twice
In a grails 2 project I'm using groovy's metaclass programming to add some methods to my domain classes.
Everything is working fine at runtime and I can run my integration tests fine.
But for unit tests I have some issues.
I have created a test mixin that is in charge of initializing the metaclass programming part.
This mixin is not running reliably:
the methods added to the metaclass are not available, or they are available after a first call, or they are available only after a previous grails test-app unit: command has been called.
This is quite a problem for continuous build.
You should be able to reproduce this issue (at least with grails 2.0.4) by
0) create a new grails projects
1) add a domain object
create-domain-class playground.Data
2) add this class to your src/groovy/playground dir
package playground
import grails.test.mixin.domain.DomainClassUnitTestMixin
import grails.test.mixin.support.GrailsUnitTestMixin
import org.codehaus.groovy.grails.commons.GrailsApplication
import org.codehaus.groovy.grails.commons.GrailsDomainClass
import org.junit.Before
class EnhanceDomainTestMixin {
boolean enhancerMethodCalled = false;
GrailsApplication application
MetaMethod mockDomainMethod
//replace the mockDomain Method from DomainClassUnitTestMixin with this closure
def enhancedMockDomain = { Class cl, List list ->
def enhanced =cl.metaClass.getMetaMethod("isEnhanced")
try {
//run the mockDomain method to have the mocked domain class registered in the grails application
mockDomainMethod.invoke(delegate, cl, list)
}
finally {
//enhance the grails domain with a new method
def domain = application.getDomainClass(cl.name) as GrailsDomainClass
domain.metaClass.isEnhanced = { return true; }
assert domain.newInstance().isEnhanced();
}
}
#Before void runDomainEnhancer() {
enhancerMethodCalled = true;
//GrailsUnitTestMixin.initGrailsApplication() should have already been called. (at least this was not an issue here)
application = GrailsUnitTestMixin.grailsApplication
//pick the mockDomain method
mockDomainMethod = DomainClassUnitTestMixin.metaClass.pickMethod("mockDomain", Class, List)
//if the picked mockDomain has never been enhanced, wrap it.
if(mockDomainMethod != enhancedMockDomain) {
DomainClassUnitTestMixin.metaClass.mockDomain = enhancedMockDomain
}
}
}
3) Add this small utils class (in test/unit/playground)
package playground
class TestSetup {
static Data d1
static void setup() {
d1 = new Data()
assert d1.isEnhanced()
}
}
4) Add these tests into the unit test already created by grails DataTests
package playground
import grails.test.mixin.*
#TestFor(Data)
#TestMixin(EnhanceDomainTestMixin)
class DataTests {
void testIsEnhancedLocal() {
assert enhancerMethodCalled
Data d = new Data()
assert d.isEnhanced()
}
void testIsEnhancedLocalSecondTime() {
assert enhancerMethodCalled
Data d = new Data()
assert d.isEnhanced()
}
void testIsEnhancedGlobalFirstTime() {
assert enhancerMethodCalled
TestSetup.setup()
assert TestSetup.d1 != null
}
void testIsEnhancedGlobalSecondTime() {
assert enhancerMethodCalled
TestSetup.setup()
assert TestSetup.d1 != null
}
}
Now run this command:
grails test-app unit:
you should have something like this output:
| Completed 4 unit tests, 4 failed in 1651ms
| Tests FAILED - view reports in target\test-reports
Now run the this command again (sometime one more is needed):
grails test-app unit: playground.DataTests
testMixin> grails test-app unit: playground.DataTests
| Completed 4 unit tests, 0 failed in 1384ms
| Tests PASSED - view reports in target\test-reports
So does anyone has a clue of why the metaClass modification is not reliable while running unit tests ? And how to workaround this issue ?
I had to use grailsApplication config in my domain class method. I ran into the same problem. Try using Holders.config instead of grailsApplication.config. It worked for me.
I'm having some problems with making my tests insert fake data in my database. I've tried a few approaches, without luck. It seems that Global.onStart is not run when running tests within a FakeApplication, although I think I read that it should work.
object TestGlobal extends GlobalSettings {
val config = Map("global" -> "controllers.TestGlobal")
override def onStart(app: play.api.Application) = {
// load the data ...
}
}
And in my test code:
private def fakeApp = FakeApplication(additionalConfiguration = (
inMemoryDatabase().toSeq +
TestGlobal.config.toSeq
).toMap, additionalPlugins = Seq("plugin.InsertTestDataPlugin"))
Then I use running(fakeApp) within each test.
The plugin.InsertTestDataPlugin was another attempt, but it didn't work without defining the plugin in conf/play.plugins -- and that is not wanted, as I only want this code in the test scope.
Should any of these work? Have anyone succeeded with similar options?
Global.onStart should be executed ONCE (and only once) when the application is launched, whatever mode (dev, prod, test) it is in. Try to follow the wiki on how to use Global.
In that method then you can check the DB status and populate. For example in Test if you use an in-memory db it should be empty so do something akin to:
if(User.findAll.isEmpty) { //code taken from Play 2.0 samples
Seq(
User("guillaume#sample.com", "Guillaume Bort", "secret"),
User("maxime#sample.com", "Maxime Dantec", "secret"),
User("sadek#sample.com", "Sadek Drobi", "secret"),
User("erwan#sample.com", "Erwan Loisant", "secret")
).foreach(User.create)
}
I chose to solve this in another way:
I made a fixture like this:
def runWithTestDatabase[T](block: => T) {
val fakeApp = FakeApplication(additionalConfiguration = inMemoryDatabase())
running(fakeApp) {
ProjectRepositoryFake.insertTestDataIfEmpty()
block
}
}
And then, instead of running(FakeApplication()){ /* ... */}, I do this:
class StuffTest extends FunSpec with ShouldMatchers with CommonFixtures {
describe("Stuff") {
it("should be found in the database") {
runWithTestDatabase { // <--- *The interesting part of this example*
findStuff("bar").size must be(1);
}
}
}
}