How to write integration tests for Micronaut APIs?
Asked Answered
W

2

5

Let's say I have an API to GET list of users. Controller -> Service -> Repository. Coming from Spring background, where for writing test for this API, in test, we could declare the repository with @Autowired and insert the user data using this repository(in data setup part). Then make that API call and then assert the response. How to do this in Micronaut?

Weightlessness answered 3/12, 2020 at 9:31 Comment(0)
G
7

You can implement an integration test in a similar fashion in Micronaut using the official Micronaut Test module.

Assuming you are using JUnit 5, here is a code sample from the Micronaut Test documentation page. (There are similar examples for Spock, KotlinTest, and Kotest, just in case you use a different testing framework.)

package io.micronaut.test.junit5;

import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

import javax.inject.Inject;

@MicronautTest 
class MathServiceTest {

    @Inject
    MathService mathService; 

    @ParameterizedTest
    @CsvSource({"2,8", "3,12"})
    void testComputeNumToSquare(Integer num, Integer square) {
        final Integer result = mathService.compute(num); 

        Assertions.assertEquals(
                square,
                result
        );
    }
}

The @MicronautTest annotation instructs the test runner to start up a Micronaut application for the test purpose. Then the @Inject annotation injects the MathService bean (similarly to the @Autowired annotation.) Now it's up to you to call the injected service to initialize the desired state before you run your assertions.

Also, take a look at this test example from the official "Access a database with JPA and Hibernate" user guide. It shows how you can use the declarative HTTP client (the one injected as @Inject @Client("/") HttpClient client) to test your endpoints against the application started with the @MicronautTest annotation. You can benefit from both styles. For instance, you can inject service (or repository) and call it to prepare the desired initial state for the integration test, or you can do exactly that by calling specific endpoints (if they exist) that can create the data you want to retrieve from the controller with the real REST API call.

UPDATE: As Sascha Frinken mentioned in the comment below, @MicronautTest wraps the test execution with the transaction by default. In this case, when you call repository.save(t) method inside the test method, it won't get committed until the transaction is completed. You can turn off this behavior with:

@MicronautTest(transactional = false)
Gui answered 3/12, 2020 at 10:39 Comment(5)
Yes, but as I inject repository, and make save call, I am suspecting the data is not persisted yet, and that's why I always get empty list. But if i do this save operation in beforeAll(), then it works. Can we do something like entityManager.flush() here?Weightlessness
Can you edit your question and provide a minimal reproducible example of what you are trying to do and what does not work specifically?Gui
Sure will create a github repo, and update the question.Weightlessness
@Weightlessness are you using @MicronatTest? If so then turn off transaction by setting @MicronatTest(transactional = false)Chaing
@SaschaFrinken perfect, thanks! Also thanks Szymon for your help too!Weightlessness
R
1

Seems we don't have a piece of code that can help us to tell you where your problem could be it's a little bit complicated to give a solid solution, even tho I think this could help you.

Every time you run your test on a micronaut app you will have available on your context the services and repositories, if you want to test that your controller > service communicates correctly you can create a @Client

I'll give you an example of a test with Spock Framework

class PersonControllerSpec extends Specification {

    @Shared @AutoCleanup EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer)
    @Shared PersonClient client
    @Shared PersonService service

    void setupSpec(){
        client = embeddedServer.applicationContext.getBean(PersonClient)
        service = embeddedServer.applicationContext.getBean(PersonService)
    }


    @Transactional
    void cleanup(){
        Person.list()*.delete()
    }

    def "/people should return 2 elements" (){
        given:
        service.save(new Person(name: "Daniel", lastName: "Araiza", age: 22, phone: "235-547-8761" ))
        service.save(new Person(name: "Omar", lastName: "Bautista", age: 32, phone: "765-234-8623"))
        when:
        List<Person> people = client.list()
        then:
        people.size() == 2
    }
}

You can see more examples on my repository a link!

Rubin answered 3/12, 2020 at 15:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.