0

I am trying to add tests to test Controller, but mock the dependencies.

@MicronautTest
class PostControllerTest(private val posts: PostRepository, @Client("/") private val client: HttpClient) : StringSpec({

    "test get posts endpoint" {
        every { posts.findAll() }
            .returns(
                listOf(
                    Post(
                        id = UUID.randomUUID(),
                        title = "test title",
                        content = "test content",
                        status = Status.DRAFT,
                        createdAt = LocalDateTime.now()
                    )
                )
            )
        val request = HttpRequest.GET<Any>("/posts")
        val bodyType = Argument.listOf(Post::class.java).type
        val response = client.toBlocking().exchange(request, bodyType)

        response.status shouldBe HttpStatus.OK
        response.body()!![0].title shouldBe "test title"

        verify(exactly = 1) { posts.findAll() }
    }

    @MockBean(PostRepository::class)
    fun posts() = mockk<PostRepository>()
})

This does not work, due to the mocked PostRepsoitory can not be recognized.

Changed to the following when running tests.

@MicronautTest
class PostControllerTest(
    private val postsBean: PostRepository,
    @Client("/") private var client: HttpClient
) : FunSpec({

    test("test get posts endpoint") {
        val posts = getMock(postsBean)
        every { posts.findAll() }
            .returns(
                listOf(
                    Post(
                        id = UUID.randomUUID(),
                        title = "test title",
                        content = "test content",
                        status = Status.DRAFT,
                        createdAt = LocalDateTime.now()
                    )
                )
            )
        val request = HttpRequest.GET<Any>("/posts")
        val bodyType = Argument.listOf(Post::class.java).type
        val response = client.toBlocking().exchange(request, bodyType)

        response.status shouldBe HttpStatus.OK
        response.body()!![0].title shouldBe "test title"

        verify(exactly = 1) { posts.findAll() }
    }
}) {
    @MockBean(PostRepository::class)
    fun posts() = mockk<PostRepository>()
}

And got the exception like this.

io.micronaut.context.exceptions.BeanInstantiationException: Error instantiating bean of type  [com.example.PostControllerTest]

Message: Retrieving the port from the server before it has started is not supported when binding to a random port
Path Taken: new DataInitializer(PostRepository posts) --> new DataInitializer([PostRepository posts]) --> new PostControllerTest(PostRepository postsBean,[HttpClient client])

The DataInitializer is used to listen StartupEvent and insert sample data. How to make sure the application is started successfully before running the tests.

The complete code is here.

There is another mock example written in Junit5 and Mockito, it works well.

Hantsy
  • 8,006
  • 7
  • 64
  • 109

1 Answers1

0

Finally I found there are two approaches to overcome this barrier.

The first is using Environment to excludes DataInitiliazer bean.

@Requires(notEnv=["mock"])
class DataInitiliazer...

And run test with a mock env.

@MicronautTest(environments = ["mock"])
class PostControllerTest(
    private val postsBean: PostRepository,
    @Client("/") private var client: HttpClient
) : FunSpec({


    test("test get posts endpoint") {
        val posts = getMock(postsBean)
        every { posts.findAll() }
            .returns(
                listOf(
                    Post(
                        id = UUID.randomUUID(),
                        title = "test title",
                        content = "test content",
                        status = Status.DRAFT,
                        createdAt = LocalDateTime.now()
                    )
                )
            )
        val response = client.toBlocking().exchange("/posts", Array<Post>::class.java)

        response.status shouldBe HttpStatus.OK
        response.body()!![0].title shouldBe "test title"

        verify(exactly = 1) { posts.findAll() }
    }
}) {
    @MockBean(PostRepository::class)
    fun posts() = mockk<PostRepository>()
}

The second approach is mocking methods in the mocked PostRepository that will be called in DataInitializer bean. At the same time, inject an EmbeddedServer and create the HttpClient bean to make sure the port is available.

@MicronautTest()
class PostControllerTest(
   private val server: EmbeddedServer,
) : FunSpec({

    test("test the server is running") {
        assert(server.isRunning)
    }

    test("test get posts endpoint") {
        val postsBean = server.applicationContext.getBean(PostRepository::class.java)
        val client = server.applicationContext.createBean(HttpClient::class.java, server.url)
        val posts = getMock(postsBean)
        every { posts.findAll() }
            .returns(
                listOf(
                    Post(
                        id = UUID.randomUUID(),
                        title = "test title",
                        content = "test content",
                        status = Status.DRAFT,
                        createdAt = LocalDateTime.now()
                    )
                )
            )
        val response = client.toBlocking().exchange("/posts", Array<Post>::class.java)

        response.status shouldBe HttpStatus.OK
        response.body()!![0].title shouldBe "test title"

        verify(exactly = 1) { posts.findAll() }
    }
}) {
    @MockBean(PostRepository::class)
    fun posts(): PostRepository {
        val mock = mockk<PostRepository>()
        justRun { mock.deleteAll() }
        every { mock.saveAll(any<List<Post>>()) } returns listOf<Post>()
        return mock;
    }
}
Hantsy
  • 8,006
  • 7
  • 64
  • 109