Ktor - Unit testing is throwing 404 - unit-testing

I have a simple program in Ktor. It runs perfectly, but when I run the unit testing class it only throws this error: "expected:<200 OK> but was:<404 Not Found>"
This is my unit test code:
class ApplicationTest {
#Test
fun testRoot() = testApplication {
val response = client.get("/")
assertEquals(HttpStatusCode.OK, response.status)
}
#Test
fun testStart() = testApplication {
val response = client.post("/start")
assertEquals(HttpStatusCode.OK, response.status)
}
}
This is my Application.kt:
const val enableHTTPS = false
const val portHTTP = 80
const val portHTTPS = 7001
lateinit var environment: ApplicationEngineEnvironment
fun main() {
initEnvironment()
embeddedServer(Netty, environment = environment).start(wait = true)
}
My routing file is:
fun Application.configureRouting() {
routing {
get {
call.respond(ApiDetails())
}
post("/start") {
call.response.status(HttpStatusCode.OK)
}
post("/move") {
val gameDetails: GameDetails = call.receive()
val move = BasicStrategy().move(gameDetails, application)
call.respond(move)
}
post("/end") {
}
}
}

To test an application, its modules should be loaded to testApplication. Loading modules to testApplication depends on the way used to create a server: by using the application.conf configuration file or in code using the embeddedServer function.
If you have the application.conf file in the resources folder, testApplication loads all modules and properties specified in the configuration file automatically.
You can disable loading modules by customizing an environment for tests.
If you use embeddedServer, you can add modules to a test application manually using the application function:
fun testModule1() = testApplication {
application {
module1()
module2()
}
}
In your case:
class ApplicationTest {
#Test
fun testRoot() = testApplication {
application {
configureRouting()
}
environment {
config = MapApplicationConfig(
"ktor.deployment.port" to "80",
"ktor.deployment.sslPort" to "7001"
)
}
val response = client.get("/")
assertEquals(HttpStatusCode.OK, response.status)
}
#Test
fun testStart() = testApplication {
application {
configureRouting()
}
environment {
config = MapApplicationConfig(
"ktor.deployment.port" to "80",
"ktor.deployment.sslPort" to "7001"
)
}
val response = client.post("/start")
assertEquals(HttpStatusCode.OK, response.status)
}
}

Related

Ktor+Koin: Why unit test still keep connect to mongodb even I mocked the service that injected with `CoroutineDatabase` instance

Note: I'm a Kotlin beginner
Developing stack
Ktor
Koin
KMongo: MongoDB
I defined my application to be like this
Route -> Service -> Repository
Route is define all HTTP request endpoints.
Service is business logic of application.
Repository is data access layer for query/persist data to MongoDB.
What I did to test a route, if the simple route like.
fun Route.healthCheck() {
get("/health") {
call.respond(HealthCheckResponse(message = "OK"))
}
}
#Test
fun testHealth() = testApplication {
application {
configureRouting()
}
client.get("/health").apply {
expect {
that(status).isEqualTo(HttpStatusCode.OK)
that(contentType()?.contentType).isNotNull().and {
contains(ContentType.Application.Json.contentType)
}
that(Json.decodeFromString(HealthCheckResponse.serializer(), bodyAsText()).message)
.isEqualTo("OK")
}
}
}
Test above will run good.
But when the case of the route that has DI, injecting CoroutineDatabase object into Repository then that repository inject it to Service and the service inject into Route.
In unit test code, I defined like below.
// Route define
fun Application.configureRouting() {
routing {
user()
}
}
fun Route.user() {
val userService: UserService by inject()
...
}
class UserServiceImpl(
private val userRepository: UserRepository // <- Repository is injected with MongoDB `CoroutineDatabase` object.
) : AccountService {
...
}
===============
// Unit Test
class UserEndpointTest: KoinTest {
#get:Rule
val koinTestRule = KoinTestRule.create {
modules(module{ single { accountService } })
}
#Test
fun testUserEndpoint() = testApplication {
application {
configureRouting() // -> collecting all extended function of `Route`
}
val client = createClient {
install(ContentNegotiation) {
json()
}
}
val testerEmail = "i_am_testing#the-email.dev"
val requestJson = """
{
"email": "$testerEmail",
"password": "aBCdEf9"
}
""".trimIndent()
val testBody = jsonMapper.decodeFromString(CreateAccountRequest.serializer(), requestJson)
coEvery { mockAccountService.submitUser(any()) } returns User(id = newId(), email = testerEmail, password = "")
client.post("$accountEndpoint/user") {
contentType(ContentType.Application.Json)
setBody(testBody)
}.apply {
expect {
that(status).isEqualTo(HttpStatusCode.Created)
that(contentType()?.contentType).isNotNull().and {
contains(ContentType.Application.Json.contentType)
}
that((body() as AccountResponse).id).isNotBlank()
that((body() as AccountResponse).email).isNotBlank().and {
isEqualTo(testerEmail)
}
}
}
}}
I expected I did mocking service and it should be inject into Route then it won't chaining call to repository and CoroutineDatabase object.
But when I run it keep connecting to database, I noticed with log below.
2022-09-07 02:51:30.093 [cluster-ClusterId{value='631788a0b273ac122eaa8350', description='null'}-localhost:27017] DEBUG org.mongodb.driver.cluster - Updating cluster description to {type=UNKNOWN, servers=[{address=localhost:27017, type=UNKNOWN, state=CONNECTING, exception={com.mongodb.MongoSocketOpenException: Exception opening socket}, caused by {java.net.ConnectException: Connection refused}}]
And I tried to find out a tutorial to find how to write Unit Test with scenario like these, but I found most of answers on searched results are before Ktor version up. (Noticed from their codes still using withTestApplication {} which is deprecated already in version 2.1.0
Who can explain the step of Koin DI work and how to interrupt it with mock.
Thanks.

android unit test, how to test the activity crated by context.startActivity(intent)

Having an activity which has some functions need to be coverage tested.
class HandlerActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle ) {
System.out.println("enter HandlerActivity.onCreate()")
doSomething(intent)
}
}
//////////////
#RunWith(RobolectricTestRunner::class)
class HandlerActivityTest {
#Test
fun test_activity() {
val conextSpy = spyk(ApplicationProvider.getApplicationContext())
var testEx: Throwable? = null
try {
val intent = Intent(this, HandlerActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
conextSpy.startActivity(intent)
Shadows.shadowOf(Looper.getMainLooper()).idle()
} catch (ex: Throwable) {
testEx = ex
}
junit.framework.Assert.assertNull(testEx)
io.mockk.verify { contextSpy.startActivity(any()) }
}
The test passed, but the HandlerActivity.onCreate() is not called.
How to unit test a onCreate() of an activity?

Unable to mock a method - Mockito - Kotlin

I am trying to mock a method (getQualityControlCheckDataForUplift(ArgumentMatchers.any(), ArgumentMatchers.anyFloat())) to return qcchecks which I have defined in my test case but I get an error.
Any suggestions on where I am doing it wrong please
org.mockito.exceptions.base.MockitoException: Unable to create mock instance of type 'QualityControlChecksDataProvider'
QualityControlChecksDataProvider
class QualityControlChecksDataProvider #Inject constructor(
private val offlineDataStorage: OfflineDataStorage,
private val app: App
) {
private val mapOfQCChecksForMilestone = LinkedHashMap<String, ArrayList<QualityControlCheck>?>()
fun getQualityControlCheckDataForUplift(qualityControlMilestone: QualityControlMilestone, uplift: Float):
ArrayList<QualityControlCheck>? {
val qcChecksForUplift: ArrayList<QualityControlCheck>? = ArrayList()
val qcChecksForMilestone = mapOfQCChecksForMilestone[qualityControlMilestone.milestoneText]
qcChecksForMilestone?.forEach {
if (it.uplift == uplift) qcChecksForUplift?.add(it)
}
return qcChecksForUplift
}
}
In my unit test this is how I am trying to mock
#Test
fun `upliftedVolumeUpdated abcd` () {
val qualityControlCheckDataProvider = spy(QualityControlChecksDataProvider::class.java)
every(qualityControlCheckDataProvider.getQualityControlCheckDataForUplift(ArgumentMatchers.any(), ArgumentMatchers.anyFloat())).thenReturn(qualityControlChecks)
}
I don't think a spy is what you want here I think you want a mock. Give this a try
#Test
fun `upliftedVolumeUpdated abcd` () {
val qualityControlDataProviderMock = mock<QualityControlChecksDataProvider> {
on { getQualityControlCheckDataForUplift(any(), any()) } doReturn qualityControlChecks
}
}

Unit test with Coroutines and Retrofit

I created an App using coroutines and retrofit and it works fine. The problem comes when I try to create UT for the Presenter. Here how I made the presenter:
class MainPresenter : ViewModel() {
private val compositeDisposable = CompositeDisposable()
private val heroesRepository: HeroesRepository = heroesRepositoryModel.instance()
private lateinit var listener: ActivityStatesListener
fun setActivityListener(listener: ActivityStatesListener) {
this.listener = listener
}
fun getHeroesFromRepository(page: Int) {
GlobalScope.launch(Dispatchers.Main) {
try {
val response = heroesRepository.getHeroes(page)
listener.onHeroesReady(response.data.results)
} catch (e: HttpException) {
listener.onError(e.message())
} catch (e: Throwable) {
listener.onError(e.message)
}
}
}
override fun onCleared() {
super.onCleared()
compositeDisposable.dispose()
}
}
I started to make the UT for it and I made a small test but is giving me the following error: java.lang.IllegalStateException: Module with the Main dispatcher had failed to initialize
class HeroesDataSourceTest {
val heroesRepository: HeroesRepository = mock(HeroesRepository::class.java)
#Mock
lateinit var activityListener: ActivityStatesListener
val hero = Heroes.Hero(1, "superman", "holasuperman", 1, null, null)
val results = Arrays.asList(hero)
val data = Heroes.Data(results)
val dataResult = Heroes.DataResult(data)
private val mainPresenter = MainPresenter()
#Before
fun initTest() {
MockitoAnnotations.initMocks(this)
}
#Test
fun testLoadInitialSuccess() = runBlocking(Dispatchers.Main) {
`when`(heroesRepository.getHeroes(0)).thenReturn(dataResult)
mainPresenter.getHeroesFromRepository(0)
verify(activityListener).onHeroesReady(dataResult.data.results)
}
}
Is clear that Dispatcher.Main is giving problems but I have no clue how to solve it.
EDIT
The repository used is the following:
class HeroesRepository {
val privateKey = "5009bb73066f50f127907511e70f691cd3f2bb2c"
val publicKey = "51ef4d355f513641b490a80d32503852"
val apiDataSource = DataModule.create()
val pageSize = 20
suspend fun getHeroes(page: Int): Heroes.DataResult {
val now = Date().time.toString()
val hash = generateHash(now + privateKey + publicKey)
val offset: Int = page * pageSize
return apiDataSource.getHeroes(now, publicKey, hash, offset, pageSize).await()
}
fun generateHash(variable: String): String {
val md = MessageDigest.getInstance("MD5")
val digested = md.digest(variable.toByteArray())
return digested.joinToString("") {
String.format("%02x", it)
}
}
}
I assume that heroesRepository.getHeroes(page) marked as suspend, so it will suspend the coroutine and not block the Main thread.
Try to follow the next approach:
// add `CoroutineContext` to the constructor to be replaceable from the tests
class MainPresenter(private val uiContext: CoroutineContext = Dispatchers.Main)
: ViewModel(), CoroutineScope {
private var job: Job = Job()
override val coroutineContext: CoroutineContext
get() = uiContext + job
fun getHeroesFromRepository(page: Int) {
// use local scope to launch a coroutine
launch {
try {
val response = heroesRepository.getHeroes(page)
listener.onHeroesReady(response.data.results)
} catch (e: HttpException) {
listener.onError(e.message())
} catch (e: Throwable) {
listener.onError(e.message)
}
}
}
override fun onCleared() {
super.onCleared()
job.cancel()
}
// ...
}
In the test class replace uiContext with another CoroutineContext:
class HeroesDataSourceTest {
// ... initializations
#Test
fun testLoadInitialSuccess() = runBlocking {
`when`(heroesRepository.getHeroes(0)).thenReturn(dataResult)
mainPresenter = MainPresenter(Dispatchers.Unconfined).apply {
getHeroesFromRepository(0)
}
// ... your tests here
}
}

Upgrading Spock unit tests from Grails 1.3.9 to Grails 2.3.9. But edit() test is failing

I am updating unit tests in a Grails project. We were originally using version 1.3.9 and now we are updating to version 2.3.9. I am using Spock.
I keep getting this error:
results:
junit.framework.AssertionFailedError: Condition not satisfied:
controller.edit() == [filterCategoryInstance: filterCategoryInstance]
| | | |
| null false John
com.xxxxxx.xxxxx.FilterCategoryController#20574000
Here is the controller code:
#Secured(["hasAnyRole('CM_ADMIN')"])
def edit() {
def filterCategoryInstance = FilterCategory.get(params.id)
if (!filterCategoryInstance) {
flash.message = "${message(code: 'default.not.found.message', args: [message(code: 'dpFilterCategory.label', default: 'FilterCategory'), params.id])}"
redirect(action: "list")
}
else {
return [filterCategoryInstance: filterCategoryInstance]
}
}
and here is the test code:
#Mock([FilterCategory, FilterCategoryTag])
#TestFor(FilterCategoryController)
#TestMixin(DomainClassUnitTestMixin)
class FilterCategoryControllerSpec extends ExtendedControllerSpec {
def 'edit action: existing FilterCategory'() {
setup:
mockI18N(FilterCategoryController)
params.id = filterCategoryInstance.id
expect:
controller.edit() == [filterCategoryInstance: filterCategoryInstance]
where:
tag = new FilterCategoryTag(name: 'tag1')
filterCategoryInstance = new FilterCategory(name: "John",
submissionText:"John", sortOrder:0, 'filterCategoryTags': [tag])
}
And here is the ExtendedControllerSpec code. I hope I have included enough code:
I have looked at the following web pages for guidance:
#Mixin(MetaClassMixin)
class ExtendedControllerSpec extends Specification {
def props
protected void setup() {
//super.setup()
props = new Properties()
File file = new File("grails-app/i18n/messages.properties")
if (file.exists()) {
def stream = new FileInputStream(file)
props.load stream
stream.close()
}
mockI18N(controller)
}
def mockI18N = { controller ->
controller.metaClass.message = { Map map ->
if (!map.code)
return ""
if (map.args) {
def formatter = new MessageFormat("")
if (props.getProperty(map.code)) {
formatter.applyPattern props.getProperty(map.code)
}
return formatter.format(map.args.toArray())
} else {
if (props && props.hasProperty(map.code)) {
return props.getProperty(map.code)
} else {
return map.code
}
}
}
}
/**
* add dynamic methods in test setup.
*/
protected void addDynamicMethods() {
registerMetaClass(String)
String.metaClass.mixin StringUtils
}
protected GrailsUser mockGrailsUser() {
return Mock(GrailsUser)
}
...
/**
* must call AFTER mockDpSercurityService
*/
protected void setHasRoleTrue() {
if (controller?.dpSecurityService?.metaClass) {
controller.dpSecurityService.metaClass.hasRole = {return true}
}
}
protected void setHasRoleFalse() {
if (controller?.dpSecurityService?.metaClass) {
controller.dpSecurityService.metaClass.hasRole = {return false}
}
}
protected void mockUserService() {
controller.dpUserService = new MockFor(UserService)
}
}
http://sanjaykanwar.blogspot.com/2012/07/grails-controller-test-with-spock.html
http://naleid.com/blog/2012/05/01/upgrading-to-grails-2-unit-testing
Looks like the if branch gets executed in edit() instead of the else branch because FilterCategory does not get saved and therfore does not get a proper id.