What Tools/Patterns do you use for Component/Integration Testing?

Hello everyone!

So we currently have both Unit-Tests and E2E Tests (front-end and API tests using Playwright).
We want to start adding Tests we can run during PR, that doesnā€™t require a deployment like E2E tests require.

To explain the context, we basically following what is being described in here:

  • Component Testing is testing your Micro-service in complete isolation. Other micro-services, databases and such - are completely mocked.

  • Integration Testing purpose is to test your Micro-service integration points. So you exclude most of the internal micro-service logic, and focus on only verify how the end points communicate (with another real Micro-service or database)

While this makes sense, it raises a LOT of questions. Hereā€™s couple of the top of my head:

  1. We run Lambdas, not containers. When testing components, are you supposed to run Lambda locally, or is it OK to just invoke the function that the endpoint invokes?

  2. How do you mock an external AWS-call? You can use Unit-Tests tools and stub the actual function that does the final call (which kinda makes it fell like Unit-Tests of multiple function instead of just one) or you can use HTTP interceptors to catch the request on the network level and fake it back? (but those normally just work with HTTP Requests I think)

  3. Whatā€™s the benefit of in-memory databases over a local Docker container for example? isnā€™t it easier to use a Test-container? Are those big databases in use these days supported in memory mode?

  4. Integration Testing sounds very complex. While you can stub your micro-service functionality, if you are actually invoking a real database or another micro-service, and you want to run those during PR - you have to spin up both, and their dependencies? as you can easily mock external service (as you donā€™t have access to the code)? This sounds like I canā€™t really test it during PRs unless Iā€™m ready to spin mini/full environment?

Etc etc. Components testing feels like an easier goal to follow, as it excludes other services and the need to spin them up, so I guess we will start there. But are there any good examples/articles out there in the wild? Bonus points if they are in the AWS-Cloud/NodeJs/Typescript world :slight_smile:

Also, I would love to hear your experiences! Thanks!

2 Likes

Each team really wants to be testing their own stack parts. We all know that mocks cost effort to maintain, and that in-memory database choices win out when it comes to platform design. I always go with whatever is most reliable and fastest, even if itā€™s not my dream test environment.

This week Iā€™m busy trying to semi-E2E a stress/load/scale test of a feature that already shipped. Only to find that not only are the interfaces and flow between the services a bit confusing, but so is the spread of knowledge across teams. I only want to plug into and test one service, but it relies on others, and obviously I donā€™t want to spin up an entire configuration in database for each test. So Iā€™m going to play with it a while before I choose my route.

I have probably been doing E2E testing and GUI testing for too long, but for me my motto is to try something, learn from it and then only make your commitment to a route. Changing your testing approach gradually over time and continuously means you get to see fresh views of the product, itā€™s not that evil a time sink. Definitely, less evil than choosing one way and thus only ever finding one class of defects that is suited to my chosen test approach only.

1 Like

Hey! Thatā€™s actually a big initiative Iā€™ve started at my company, doing integration testing. So this is near and dear to me. As my company is trying to align closer to the Test Pyramid. So minimizing our E2E tests and focus on Unit and Integrations

  1. Iā€™m not familiar with Lambdas, as my company does work in containers. So thanks for saying something new for me to look into!

  2. When it comes to mocking external calls, we adopted WireMock. We have it structed where we have a boolean(true/false) trigger to say (true)ā€˜make a real callā€™ and (false) use the mocks. We have it structured in a way so that when we do a real call (true). We have WireMock listen to all the traffic and automagically build out external API mocks. Although, to be honest, you can just build out the Given/Then calls for mocking. So that way when a Given endpoint is called with a Given payload. It will Then return a pre-defined response instead of doing a real call. This is nice so we can control the API calls and responses. However the downfall is, if the external endpoints change signatures/responses. WireMock doesnā€™t know about this.

2a, To solve for that aspect if an external endpoint changes contract or signatures. We are now starting to proof out PactFlow.io for contracts. So that way we can verify that the endpoint isnā€™t changing in a way that will break our connections. So we have WireMock which is about isolation, and PactFlow for external contract checks.

  1. Speed. In-memory are much smaller overhead, generally can be easier to maintain and much much faster. I utilize TestContainers so we can have a mimic of our production microservices as close as possible so we can verify the SQL procedures and checks as our Production environment would work. We definitely notice some longer build times due to this, and weā€™re investigating ways of minimizing the impact.

  2. It can definitely be a tough one to navigate and Iā€™m still running into issues and constantly question the smarter people of ā€˜is what weā€™re doing worth it compared to xyzā€™ Our integration tests run as part of our CICD pipeline. So that way we can just on doing exploratory and Acceptance Criteria checking in PR environments. Since in theory our function and code has all been tested before that point.

1 Like

Hey, great question and Iā€™ll try and offer some guidance based on my experienceā€¦

As mentioned in the previous answer by @sharmon contract tests are a great PR / pre merge test with a high amount of value that can be run for quick feedback - more so in a microservices architecture. You can quickly verify the external contracts / API specs between consumers and providers without having to spin them up or deploy them. Pact is a popular framework for doing contract testing and has implementations in all popular languages (including Typescript). Disadvantages of this approach is that it can be complex to setup initially, youā€™ll need a server to host all the contracts and it works best with internal microservices your teamā€™s/company have control ver - if you rely on a lot third party services it may not work so well. It needs all teams developing microservices to be keeping their contracts up to date for it to work well.

For the component test level, with AWS specifically, I remember using a framework / tool called LocalStack which allowed you mock out any AWS service you needed. E.g. S3, Lambdas Dynamo etcā€¦ which helped in terms of testing a microservice in isolation, locally without having to deploy it out to AWS. But this was some years ago and Iā€™m not sure where LocalStack is now and whether itā€™s the best framework to use for this.

Hopefully that helps, good luck on your journey!

Ahmed

1 Like

Thanks for the answers everybody!

When it comes to mocking external calls, we adopted WireMock.

@sharmon I keep seeing WireMock mentioned fondly, but didnā€™t yet sink time to learn how it works. From what your saying it sounds like itā€™s sort sort of a gateway between services, that records network calls, and you can turn off the real network calls when you want and replaced with those records? So itā€™s sort of mocks but you donā€™t specifically state the mocks but collect them by running real scenarios?

Contract testing is indeed something we look at, we played around Pact as a POC already :slight_smile:

Speed. In-memory are much smaller overhead, generally can be easier to maintain and much much faster.

When we say ā€œin-memoryā€ - is it a version of that database supplied by the company that wrote the database? or ā€œin-memoryā€ basically means saving a object in memory and manipulate you code to somehow consume it?

For the component test level, with AWS specifically, I remember using a framework / tool called LocalStack which allowed you mock out any AWS service you needed

Thank you! will check out LocalStack!

1 Like

We have had great success at my company with it, although weā€™re still in the infancy of this. Itā€™s worked great for us. So you can structure it either way, building mocks or the ā€˜gatewayā€™ system you described. We created our own wrapper library around WireMock to do this gateway. So I can dictate whether or not I want a real call out or not, and each Real Call is listened to and a new mock is generated if any changes happen. If I want a ultra real call that I can view on our webpages I would comment out the TestContainer so it hits the real database.

For in memory I have used it as the latter explanation you had. An object saved in memory and manipulate it. So for my tests Iā€™ll bring in an ā€˜IDatabaseRepositoryā€™ and when I build the interface in my test class. Instead of it using real calls Iā€™ll just have it return whatever Iā€™m expecting. Itā€™s more of an in-memory fake than a real database in my case. You can see more of what I mean here with the ā€œSliceā€ method, as this is what I based my implementation off of https://youtu.be/dV3SSY7I9VM?t=3251

Thank you @sharmon . I briefly looked on the video on the way home, and it seems by ā€œSliceā€ his basically introducing dependency inversion? where you pass the object to the class and then you can pass a mocked options of that data?

Are you using and ā€œrealā€ data-bases? like, Test-Container that saves data on the RAM rather than Disk? Thatā€™s also considered In-memory a lot right?

@okiba Possibly, Iā€™m still pretty new to all of this and Iā€™ve just been diving in and learning and applying stuff Iā€™ve learned. Although to me, itā€™s basically removing the database calls from the method and making them their own private methods, That way for the test you can just pass the interface into test suite and create a mock/fake/stub of it and have it return what you need for the test to continue.

For the testContainer part, With the size of the databases and how this documentation lists it . I would say they save the Disk. Ryuk (the testcontainer manager) deletes the container and cleans up the volumes after each test suite runs.

1 Like

Yep. What you describe is called ā€œdependency inversionā€ (or injection, depends on the side your looking at). You take the dependency, and you pass it to the class, instead of initialize it in the class (therefore able to pass mocks).

Thanks for sharing the documentation about in-memory! sounds like indeed TestContainer is the prefered option, at least by TestContainers :wink:

Thanks!

1 Like