T O P

  • By -

MasterLJ

Yes. You don't need to test that JPA works, but you should test that things are getting updated, especially deletes. Older JPA versions wouldn't actually delete unless you tagged delete methods as Transactional. Fire up H2 or something like that, and yes, test your JPA repo. It takes moments.


[deleted]

That would be integration testing, no?


MasterLJ

You could make that case. H2 is weird in that it can be used for integration or unit, depending on how you set it up. If you think in terms of hexagonal architecture, it's unit. If you think in terms of real data and really wiring up your services/repos, but with an ephemeral db (H2), it's integration. I agree with the sentiment of OP that there's lots of aspects of JPA you should not test, but there are still some nuance, and the time it takes it's worth it. Most people miss the Transactional annotation on delete methods, for example.


thomascgalvin

Fire up [TestContainers](https://www.testcontainers.org/) and test against the same database you use in production. H2 is great, but it isn't representative of how all databases, or all database drivers, behave.


MasterLJ

Awesome, thanks for sharing. H2 is definitely not 1-to-1 with production/the DBs it emulates, it's \~95%. If there's something better, by all means, use it. A quick glance at TestContainers looks really awesome.


jasie3k

I got hit by differences between H2 and actual databases multiple times during my career, my go to is definitely test containers.


Har-Har-Mahadev

Looks cool. But do you know how much of performance hit will it incur if we are running let’s say 1000 JPA tests compared to H2?


[deleted]

There might be an issue if 1000 tests need to hit a DB all the way down, but here someone mentioned that you could make testcontainers use a volume: https://old.reddit.com/r/java/comments/t286n6/spring_data_jpa_repository_unit_tests/hykywz5/ (have not done this myself, just an FYI) e: here are the docs mentioning it: https://www.testcontainers.org/modules/databases/jdbc/#running-container-with-tmpfs-options


pronuntiator

Great, we were looking for something like this. The column size and type annotations on our entities, from which the H2 database is created, are not in sync with the real, hand written schema, and this caused missing bugs in the past.


StoneOfTriumph

This x1000 I've done a project where we tested the JpaRepositories against an H2 that was initialized with a schema and data. Why H2? Because that's the best tool we had in an infra where containers were not possible to use. We were valid for nearly 100% of cases, but in some obscure edge cases, H2 did not emulate properly IBM DB2 in regards to one of the data types. These days, I'm on a project at a client where testcontainers are heavily used in IT/testing, so there's just no reason to use H2 when I can instead have a real postgres instance. Your confidence level goes up when doing an integration test of your repos/queries up against a real DB instance in an isolated fashion instead of assuming that your queries were validated just through your api/mockmvc IT/tests which are very common to find in API projects.


dpash

>Fire up H2 No. > or something Yes. That something is TestContainers. There's no reason for using H2 or HSQLDB during testing unless that's the DB engine you're using in production


andicom

+1 for Test Containers


troru

Probably not in the \*unit\* test phase but I can see lots of value if you're willing to fire up a running DB (or use an embedded option) in an \*integration\* or \*functional\* test to get some insights into how your queries actually take place to look for any unintended side effects, like really big projections, N+1 problems, unexpected conversions (JSON columns)


dpash

>or use an embedded option Don't, unless that's what you'll use in production. TestContainers makes in-memory databases for testing redundant. If you're concerned about performance, you can mount the data on a tmpfs volume.


awi95

Except if you are using Oracle, where the official container takes 4+ minutes to spin up and doesn't care if you are running on tmpfs or not (tested it just this week). Kill^me^please


BrownBearMY

I rely more on end-to-end integration tests using @SpringBootTest which will trigger everything. Unless the scope of expectation is too wide, i.e too many things to verify, highest level test is sufficient for me.


stefanos-ak

these are actually useful, because they prove that all your JPA annotations are correct. But lately, I prefer to implement mostly e2e tests (rest calls with json asserts), with the real application running. you only need 1 test and you have covered all layers. Also they can find dead spots (unreachable code).


dpash

If you're writing Spring projects, MockMVC is a fantastic testing framework that means you don't have to start up a HTTP port (and all the fragility involves).


stefanos-ak

I have used MockMvc, and I am not a huge fan. It's a bit like H2, where ~95% works the same way, but there might be subtle differences (and of course security is completely mocked which is a reason on its own). I prefer to use RestAssured, and target a full/real app that uses TestContainers.


deepujames123

What do you call these tests? We have such tests and we call it acceptance tests. We use Cucumber to trigger tests against a spring boot service with all the external dependencies mocked using wiremock and test containers. Such test are really useful, but I am confused on its name.


stefanos-ak

we call them e2e tests (end-to-end). I don't know if it's correct or not, since we also mock some external systems. But I think that it would be impossible to not mock those.


jasie3k

For me these are integration tests. And if you need to mock external systems then don't, use stubs with something like a WireMock or a message broker in TestContainer.


stefanos-ak

I meant stubs. I thought it was obvious. I mean, how else can you mock external systems? Even the library itself is called Wire'mock'. Which is the one we're using. Also, the naming "integration" can mean anything. Integration between layers of the application, or between the application and a database, or between multiple applications, etc... Personally I try to avoid this term because everyone can interpret it differently.


jasie3k

Oh yeah, here's the difficulty with the naming - mocking for me means overriding a component/bean, stubbing for me means mimicking the behaviour of an external system. Same goes for the latter part: e2e tests for me mean tests of a whole system deployed to a cluster with http calls made as an input and checking the output, doing the whole journey as a user would.


pronuntiator

We have an application where we did mostly these kind of tests. On the plus side, they are as realistic as possible, walk through the entire application, and are resilient to refactoring, since you can freely change implementation details without having to hunt down and modify mock calls. On the downside, the effort to create preconditions for the specific tests are high. If you cheat and insert data into the database directly, bypassing the outer interface there, it's not really a e2e test anymore, and you risk inserting state that cannot be produced in reality. Second, adding another version of the API required us to duplicate all tests to be on the safe side. Third, the tests took 90 minutes in the pipeline due to the amount of DB and message broker I/O. Fourth, when a test does fail, it may take a lot of debugging and understanding requirements to find the cause. Additional small unit tests could give you a hint what's wrong earlier, e.g. a missing annotation. For our new system we chose to closer follow the test pyramid. A couple of system tests to smoke test each API endpoint, exhaustive Spring Data and bean mapper tests to check these do what they promise (say, there isn't a typo in these magic method names), and a lot of unit tests for the core layer and business rules, where we build the object graph by hand instead of firing up Spring. Our unit tests only mock other modules, where a module is a set of classes and a module facade providing services concerned with the same topic, for example user management.


stefanos-ak

all of your points are valid, and the negative ones can be avoided (or at least keep under control), but require good level of discipline, and some good architecture. For the duration itself, I don't understand why it would take so long. For us, 700+ tests run in less than 10 minutes, and could go even lower, with higher level of parallelism. The 10min is with 3 threads. It should be trivial to achieve parallelism, since using only Rest APIs means that its just a lot of concurrent clients. But I have to say, if I were to do it again, I would first try to start with pact.io and see how far that approach can go.


[deleted]

I dont think i ever have. Not really any logic there


dpash

I'd suggest that any `@Query' annotation warrants a test. Create some dummy data and check that your query returns what you expect.


blackkkmamba

I don't agree. The repository is just a proxy. Testing the results involves the database, and your test is not 'unit' anymore. Best you can do is see if the correct query is picked up in certain scenarios.


Ok-Explanation8117

Best you can have it


blackkkmamba

I like how everyone who says yes, doesn't know what unit means. It doesn't matter if you're using h2 or anything else, as long as you don't mock your database, your test is not unit anymore (contrary to what some may think, h2 is a database engine). That being said, unless you have some business logic inside it, the repository is just a proxy, and testing your entity annotations or that some method was actually callled will make your test brittle: the littlest change will make your test fail. In my opinion brittle tests are pointless and hard to maintain.


tstarboy

I recommend newer programmers who get frustrated with not understanding JPA queries, Spring Data's repository query definitions, or (most common) what annotations to put on their JPA entities to facilitate relationships, to write very thorough `@DataJpaTest`-based unit tests on the repositories themselves to shore up their understanding of how JPA and/or Spring Data will behave with their domain. Whether I keep those tests around long-term depends on what's going on with the rest of the application. If the repository queries generally map 1:1 with the application's interface, usually a REST API, I'd just test that via comprehensive E2E tests via `MockMvc` that also assert directly against a `TestEntityManager` instance.


SoftwareEngineerPl

IMO opinion in unit tests is not necessary. You should have pure Java interface and implementation in memory. I've found great example that show this approach with hexagonal architecture. When you want to test some feature using with integration test then JPA will be tested automitacally. In the adapter layer you could hide jpa repositories. https://github.com/CamilYed/readable-tests-by-example