Skip to content

Commit

Permalink
HAI Remove @MockkBean and @SpykBean annotations
Browse files Browse the repository at this point in the history
Configure the Spring Context in integration tests to use a mockk
instance of CableReportService so the mock can be `@Autowired`.
CableReportService was defined in several integration tests as a
`@MockkBean` dependency. The actual implementation was only used in
`CableReportServiceITests`, which constructs its own custom instance.

The other class that was injected with `@MockkBean` was
`EmailSenderService`. Since there's an easy-to-use extension for mocking
an actual email server, it felt better to use that instead of mocking
the service universally. Also, unlike `CableReportServiceITests`,
`EmailSenderServiceITest` uses the Spring context, so a context with the
actual implementation is needed anyway.

Each unique combination of `@MockkBeans` and `@SpykBeans` (or the Mockito
equilevants) makes Spring create a new context for the test. While not
a huge time sink, creating (and destroying) the contexts adds up. We can
clearly see how many different contexts are created from the number of
SQL connection pools that need to be shut down at the end of the tests.

There are situations when they have to be used, but using alternative
approaches is usually better.

New contexts are also created whenever the configuration parameters are
different between tests. This is sometimes unavoidable when we need to
test how the application behaves with different configurations.

Ever since #397 we've been able to use application.test.properties to
configure the test environment, so we don't need to use test-specific
properties to diverge from the production configuration. There was a
properties parameter like remaining in `TestDataServiceITest`, so that
was removed.

There's one more service test with custom properties
(`EmailSenderServicePropertiesITest`), but in that test we're interested
in testing that the properties have the effect we want. And the
properties are different from how we generally want to configure
EmailSenderService in tests. So the custom properties are unavoidable.

Controller tests are cheaper to create with varying configurations.
Since only a very limited context is created for running them,
recreating it is not very expensive. So testing things like feature
flags on the controller level doesn't seem problematic.
  • Loading branch information
corvidian committed Nov 24, 2023
1 parent c0a8c58 commit b556a3f
Show file tree
Hide file tree
Showing 16 changed files with 134 additions and 144 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import assertk.assertions.isNotNull
import assertk.assertions.isNull
import assertk.assertions.messageContains
import assertk.assertions.prop
import com.ninjasquad.springmockk.MockkBean
import fi.hel.haitaton.hanke.allu.ApplicationStatus
import fi.hel.haitaton.hanke.allu.CableReportService
import fi.hel.haitaton.hanke.application.ALLU_USER_CANCELLATION_MSG
Expand Down Expand Up @@ -123,10 +122,9 @@ class HankeServiceITests(
@Autowired private val fileClient: MockFileClient,
@Autowired private val hankeFactory: HankeFactory,
@Autowired private val hankeAttachmentFactory: HankeAttachmentFactory,
@Autowired private val cableReportService: CableReportService,
) : DatabaseTest() {

@MockkBean private lateinit var cableReportService: CableReportService

@BeforeEach
fun clearMocks() {
clearAllMocks()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package fi.hel.haitaton.hanke.application
import assertk.Assert
import assertk.all
import assertk.assertThat
import assertk.assertions.contains
import assertk.assertions.containsExactly
import assertk.assertions.containsExactlyInAnyOrder
import assertk.assertions.each
Expand All @@ -21,8 +22,6 @@ import assertk.assertions.prop
import com.icegreen.greenmail.configuration.GreenMailConfiguration
import com.icegreen.greenmail.junit5.GreenMailExtension
import com.icegreen.greenmail.util.ServerSetupTest
import com.ninjasquad.springmockk.MockkBean
import com.ninjasquad.springmockk.SpykBean
import fi.hel.haitaton.hanke.DatabaseTest
import fi.hel.haitaton.hanke.HankeEntity
import fi.hel.haitaton.hanke.HankeNotFoundException
Expand All @@ -43,8 +42,8 @@ import fi.hel.haitaton.hanke.application.ApplicationType.CABLE_REPORT
import fi.hel.haitaton.hanke.asJsonResource
import fi.hel.haitaton.hanke.asUtc
import fi.hel.haitaton.hanke.domain.Hankealue
import fi.hel.haitaton.hanke.email.ApplicationNotificationData
import fi.hel.haitaton.hanke.email.EmailSenderService
import fi.hel.haitaton.hanke.email.EmailSenderService.Companion.translations
import fi.hel.haitaton.hanke.email.textBody
import fi.hel.haitaton.hanke.factory.AlluDataFactory
import fi.hel.haitaton.hanke.factory.AlluDataFactory.Companion.asianHoitajaCustomerContact
import fi.hel.haitaton.hanke.factory.AlluDataFactory.Companion.cableReportWithoutHanke
Expand Down Expand Up @@ -82,6 +81,7 @@ import fi.hel.haitaton.hanke.permissions.Kayttooikeustaso
import fi.hel.haitaton.hanke.permissions.Kayttooikeustaso.HAKEMUSASIOINTI
import fi.hel.haitaton.hanke.permissions.PermissionService
import fi.hel.haitaton.hanke.permissions.kayttajaTunnistePattern
import fi.hel.haitaton.hanke.test.Asserts.hasReceivers
import fi.hel.haitaton.hanke.test.Asserts.hasSingleGeometryWithCoordinates
import fi.hel.haitaton.hanke.test.Asserts.isRecent
import fi.hel.haitaton.hanke.test.AuditLogEntryEntityAsserts.hasUserActor
Expand All @@ -98,6 +98,7 @@ import io.mockk.justRun
import io.mockk.verify
import io.mockk.verifyOrder
import io.mockk.verifySequence
import jakarta.mail.internet.MimeMessage
import java.time.OffsetDateTime
import java.time.ZonedDateTime
import org.geojson.Polygon
Expand Down Expand Up @@ -132,12 +133,10 @@ private val dataWithoutAreas = createCableReportApplicationData(areas = listOf()
@WithMockUser(USERNAME)
class ApplicationServiceITest : DatabaseTest() {

@MockkBean private lateinit var cableReportServiceAllu: CableReportService
@SpykBean private lateinit var emailSenderService: EmailSenderService

@Autowired private lateinit var applicationService: ApplicationService
@Autowired private lateinit var hankeService: HankeService
@Autowired private lateinit var permissionService: PermissionService
@Autowired private lateinit var cableReportServiceAllu: CableReportService

@Autowired private lateinit var applicationRepository: ApplicationRepository
@Autowired private lateinit var hankeRepository: HankeRepository
Expand Down Expand Up @@ -638,15 +637,10 @@ class ApplicationServiceITest : DatabaseTest() {
USERNAME,
)
setAlluFields(applicationRepository.findById(application.id!!).orElseThrow())
val capturedNotifications = mutableListOf<ApplicationNotificationData>()
justRun { cableReportServiceAllu.update(21, any()) }
every { cableReportServiceAllu.getApplicationInformation(any()) } returns
createAlluApplicationResponse(21)
justRun { cableReportServiceAllu.addAttachment(21, any()) }
justRun { emailSenderService.sendHankeInvitationEmail(any()) }
justRun {
emailSenderService.sendApplicationNotificationEmail(capture(capturedNotifications))
}
val updatedApplication =
cableReport.copy(
representativeWithContacts = asianHoitajaCustomerContact,
Expand All @@ -657,11 +651,11 @@ class ApplicationServiceITest : DatabaseTest() {

applicationService.updateApplicationData(application.id!!, updatedApplication, USERNAME)

val capturedNotifications = getApplicationNotifications()
assertThat(capturedNotifications)
.areValid(application.applicationType, application.hankeTunnus)
assertThat(capturedNotifications)
.extracting { it.recipientEmail }
.containsExactlyInAnyOrder(
assertThat(capturedNotifications.toTypedArray())
.hasReceivers(
"[email protected]",
asianHoitajaCustomerContact.contacts[0].email,
rakennuttajaCustomerContact.contacts[0].email
Expand All @@ -671,8 +665,6 @@ class ApplicationServiceITest : DatabaseTest() {
cableReportServiceAllu.update(21, any())
cableReportServiceAllu.addAttachment(any(), any())
}
verify(exactly = 3) { emailSenderService.sendHankeInvitationEmail(any()) }
verify(exactly = 3) { emailSenderService.sendApplicationNotificationEmail(any()) }
}

@Test
Expand Down Expand Up @@ -714,7 +706,7 @@ class ApplicationServiceITest : DatabaseTest() {
cableReportServiceAllu.update(21, any())
cableReportServiceAllu.addAttachment(any(), any())
}
verify { emailSenderService wasNot Called }
assertThat(greenMail.receivedMessages).isEmpty()
}

@Test
Expand Down Expand Up @@ -1198,16 +1190,14 @@ class ApplicationServiceITest : DatabaseTest() {
CableReportWithoutHanke(CABLE_REPORT, cableReportData),
USERNAME,
)
val capturedEmails = mutableListOf<ApplicationNotificationData>()
every { cableReportServiceAllu.create(any()) } returns 26
every { cableReportServiceAllu.getApplicationInformation(any()) } returns
createAlluApplicationResponse(26)
justRun { cableReportServiceAllu.addAttachment(26, any()) }
justRun { emailSenderService.sendHankeInvitationEmail(any()) }
justRun { emailSenderService.sendApplicationNotificationEmail(capture(capturedEmails)) }

applicationService.sendApplication(application.id!!, USERNAME)

val capturedEmails = getApplicationNotifications()
assertThat(capturedEmails).hasSize(3) // 4 contacts, but one is the sender
assertThat(capturedEmails)
.areValid(application.applicationType, application.hankeTunnus)
Expand All @@ -1216,8 +1206,6 @@ class ApplicationServiceITest : DatabaseTest() {
cableReportServiceAllu.addAttachment(any(), any())
cableReportServiceAllu.getApplicationInformation(any())
}
verify(exactly = 3) { emailSenderService.sendHankeInvitationEmail(any()) }
verify(exactly = 3) { emailSenderService.sendApplicationNotificationEmail(any()) }
}

@Test
Expand Down Expand Up @@ -1856,16 +1844,24 @@ class ApplicationServiceITest : DatabaseTest() {
}
}

private fun Assert<List<ApplicationNotificationData>>.areValid(
type: ApplicationType,
hankeTunnus: String?
) = each { data ->
data.transform { it.senderEmail }.isEqualTo(hakijaContact.email)
data.transform { it.senderName }.isEqualTo(hakijaContact.name)
data.transform { it.applicationIdentifier }.isEqualTo(defaultApplicationIdentifier)
data.transform { it.applicationType }.isEqualTo(type)
data.transform { it.recipientEmail }.isIn(*expectedRecipients)
data.transform { it.hankeTunnus }.isEqualTo(hankeTunnus)
private fun getApplicationNotifications() =
greenMail.receivedMessages.filter {
it.subject.startsWith("Haitaton: Sinut on lisätty hakemukselle")
}

private fun Assert<List<MimeMessage>>.areValid(type: ApplicationType, hankeTunnus: String?) {
each { it.isValid(type, hankeTunnus) }
}

private fun Assert<MimeMessage>.isValid(type: ApplicationType, hankeTunnus: String?) {
prop(MimeMessage::textBody).all {
contains("${hakijaContact.name} (${hakijaContact.email}) on tehnyt")
contains("hakemukselle $defaultApplicationIdentifier")
contains("on tehnyt ${type.translations().fi}")
contains("hankkeella $hankeTunnus")
}

transform { it.allRecipients.first().toString() }.isIn(*expectedRecipients)
}

private fun initializedHanke(): HankeEntity =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import assertk.assertions.isEmpty
import assertk.assertions.isEqualTo
import assertk.assertions.isNotNull
import assertk.assertions.isPresent
import com.ninjasquad.springmockk.MockkBean
import fi.hel.haitaton.hanke.ALLOWED_ATTACHMENT_COUNT
import fi.hel.haitaton.hanke.DatabaseTest
import fi.hel.haitaton.hanke.HankeEntity
Expand Down Expand Up @@ -67,7 +66,7 @@ private const val ALLU_ID = 42
@ActiveProfiles("test")
@WithMockUser(USERNAME)
class ApplicationAttachmentServiceITest : DatabaseTest() {
@MockkBean private lateinit var cableReportService: CableReportService
@Autowired private lateinit var cableReportService: CableReportService
@Autowired private lateinit var applicationAttachmentService: ApplicationAttachmentService
@Autowired private lateinit var attachmentContentService: ApplicationAttachmentContentService
@Autowired private lateinit var applicationAttachmentRepository: ApplicationAttachmentRepository
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.security.test.context.support.WithMockUser
Expand All @@ -30,6 +31,7 @@ import org.springframework.test.context.ActiveProfiles
@SpringBootTest
@ActiveProfiles("test")
@WithMockUser(USERNAME)
@ExtendWith(MockFileClientExtension::class)
class AttachmentUploadServiceITest(
@Autowired private val attachmentUploadService: AttachmentUploadService,
@Autowired private val attachmentRepository: HankeAttachmentRepository,
Expand All @@ -41,7 +43,6 @@ class AttachmentUploadServiceITest(

@BeforeEach
fun setup() {
fileClient.recreateContainers()
mockClamAv = MockWebServer()
mockClamAv.start(6789)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import org.springframework.http.MediaType
import org.springframework.security.test.context.support.WithMockUser
import org.springframework.test.context.ActiveProfiles

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@SpringBootTest
@ActiveProfiles("test")
@WithMockUser(USERNAME)
@ExtendWith(MockFileClientExtension::class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,19 @@ import fi.hel.haitaton.hanke.attachment.DEFAULT_DATA
import fi.hel.haitaton.hanke.attachment.FILE_NAME_PDF
import fi.hel.haitaton.hanke.attachment.USERNAME
import fi.hel.haitaton.hanke.attachment.azure.Container.HANKE_LIITTEET
import fi.hel.haitaton.hanke.attachment.body
import fi.hel.haitaton.hanke.attachment.common.AttachmentInvalidException
import fi.hel.haitaton.hanke.attachment.common.AttachmentNotFoundException
import fi.hel.haitaton.hanke.attachment.common.HankeAttachmentContentRepository
import fi.hel.haitaton.hanke.attachment.common.HankeAttachmentEntity
import fi.hel.haitaton.hanke.attachment.common.HankeAttachmentRepository
import fi.hel.haitaton.hanke.attachment.common.MockFileClient
import fi.hel.haitaton.hanke.attachment.common.MockFileClientExtension
import fi.hel.haitaton.hanke.attachment.response
import fi.hel.haitaton.hanke.attachment.successResult
import fi.hel.haitaton.hanke.factory.AttachmentFactory
import fi.hel.haitaton.hanke.factory.HankeAttachmentFactory
import fi.hel.haitaton.hanke.factory.HankeFactory
import fi.hel.haitaton.hanke.factory.HankeIdentifierFactory
import java.time.OffsetDateTime
import java.util.UUID
import okhttp3.mockwebserver.MockWebServer
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
Expand All @@ -61,25 +55,10 @@ class HankeAttachmentServiceITests(
@Autowired private val hankeAttachmentFactory: HankeAttachmentFactory,
) : DatabaseTest() {

private lateinit var mockClamAv: MockWebServer

@BeforeEach
fun setup() {
mockClamAv = MockWebServer()
mockClamAv.start(6789)
}

@AfterEach
fun tearDown() {
mockClamAv.shutdown()
}

@Nested
inner class GetMetadataList {
@Test
fun `getMetadataList should return related metadata list`() {
mockClamAv.enqueue(response(body(results = successResult())))
mockClamAv.enqueue(response(body(results = successResult())))
val hanke = hankeFactory.saveEntity()
(1..2).forEach { _ ->
hankeAttachmentRepository.save(
Expand Down Expand Up @@ -150,7 +129,6 @@ class HankeAttachmentServiceITests(
assertThat(result.fileName).isEqualTo(FILE_NAME_PDF)
assertThat(result.contentType).isEqualTo(APPLICATION_PDF_VALUE)
assertThat(result.bytes).isEqualTo(DEFAULT_DATA)
assertThat(mockClamAv.requestCount).isEqualTo(0)
}
}
}
Expand All @@ -159,7 +137,6 @@ class HankeAttachmentServiceITests(
inner class SaveAttachment {
@Test
fun `Should return metadata of saved attachment`() {
mockClamAv.enqueue(response(body(results = successResult())))
val hanke = hankeFactory.save()
val blobPath = blobPath(hanke.id)

Expand Down Expand Up @@ -188,7 +165,6 @@ class HankeAttachmentServiceITests(

@Test
fun `Should throw if attachment amount is exceeded`() {
mockClamAv.enqueue(response(body(results = successResult())))
val hanke = hankeFactory.saveEntity()
(1..ALLOWED_ATTACHMENT_COUNT)
.map { AttachmentFactory.hankeAttachmentEntity(hanke = hanke) }
Expand All @@ -210,8 +186,6 @@ class HankeAttachmentServiceITests(

@Test
fun `Should fail when there is no related hanke`() {
mockClamAv.enqueue(response(body(results = successResult())))

assertFailure {
hankeAttachmentService.saveAttachment(
hankeTunnus = "HAI-123",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package fi.hel.haitaton.hanke.configuration

import fi.hel.haitaton.hanke.allu.CableReportService
import io.mockk.mockk
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration
class MockCableReportService {
@Bean fun cableReportService(): CableReportService = mockk()
}
Loading

0 comments on commit b556a3f

Please sign in to comment.