DEV Community

Cover image for Unit-Testing: The Unsung Hero of Code Quality 🛡️
Rahul Ladumor
Rahul Ladumor

Posted on • Updated on

Unit-Testing: The Unsung Hero of Code Quality 🛡️

Why Skipping Unit Tests is Like Jumping Out of an Airplane Without a Parachute 🪂

Hey folks! Today, we're diving deep into the world of unit testing. A topic as exciting as a caffeine-induced all-nighter, or as dreadful as a null pointer exception, depending on how you see it. But jokes aside, it’s essential. 🚀

Why Do We Need Unit Tests Anyway? 🤷‍♂️

First off, let’s get one thing straight: coding without unit testing is like playing Russian roulette with your project. Sure, you might survive, but is it worth the risk?

Unit tests act as your first line of defense against bugs, allowing you to catch issues early on. Think of it like a spell-checker for your code, continuously validating that your latest commits aren't breaking existing functionality.

In Practice:

Imagine you're building a serverless function in AWS Lambda to calculate the total cost of items in a shopping cart. With unit tests, you can mock various scenarios, ensuring the function handles tax calculations, discounts, and even edge-cases like zero items effectively.

const calculateTotal = require('./calculateTotal');

test('calculates total price', () => {
  const cartItems = [
    { item: 'Apple', price: 1.20 },
    { item: 'Banana', price: 0.80 }
  ];
  expect(calculateTotal(cartItems)).toBe(2.00);
});
Enter fullscreen mode Exit fullscreen mode

The Good Stuff: Pros of Unit Testing 🌈

  • Immediate Feedback: With tools like Jest in a Node.js environment, you get immediate feedback. Anytime you save a file, tests run automatically.

  • Code Confidence: You get a safety net, making future changes less risky and easier to implement.

  • Simplified Debugging: When a test fails, you only need to consider the latest changes, making debugging simpler.

  • Improved Design: Often, the need to make code testable results in better software design.

The Not-So-Good: Cons of Unit Testing 😬

  • Time Consuming: Writing tests can be time-consuming. However, consider this an investment; the time you spend now will save you debugging time in the long run.

  • Learning Curve: Setting up your testing environment and learning the syntax can be intimidating, but totally worth it.

  • False Sense of Security: Passing tests aren’t a 100% guarantee that your code is bug-free. Integration tests and end-to-end tests are essential too.

The Bottom Line 🎯

If you're coding in a professional setting, especially if you're juggling complex systems with AWS and serverless technologies, unit tests aren't optional; they're a must. The initial effort will pay off in terms of maintainability, robustness, and peace of mind.

Some Cool Tools 🛠️

  • Jest for Node.js
  • Mocha for browser-based code
  • Jasmine for those in love with BDD (Behavior-Driven Development)

Round-Off 🌠

That’s it, folks! Happy coding, and may your builds always be green and your coffee cup forever full! ☕🚀

Don’t forget to smash that like button If you found this blog useful and want to learn more, here are some ways you can keep in touch:

Top comments (18)

Collapse
 
adaptive-shield-matrix profile image
Adaptive Shield Matrix • Edited

I do not think it is intimidating, in total you only need to know 2 methods

  • "test" - an empty wrapper that does nothing, only giving a separate name to the test and
  • "expect" - to compare values, comparing methods can be chained / auto-completed after calling expect on an object/value.

Unit test best practice:

  • write logic in functions, only using object state (no library stuff, components or anything else that has to be mocked)
  • write/commit explicit small test data: common cases and all edge cases
  • write core logic in a functional style
  • if you have to mock anything, then you are doing it wrong. Mocks significantly lower/decrease/slow down your feedback loop and make you hate tests. If tests run fast or instantly it is not a hassle but a joy to use instead.

About time consuming -> I would argue that you just pay upfront the time you would have spend debugging the same edge cases.

  • You can either discover them (bugs, edge cases) yourself, or let them be discovered by your tester or (worst case) your users.
  • You just have to explicitly model (and think about), and write down (more easily done with a functional programming style) all edge cases.
  • You have to move everything logic based outside of components and inside pure functions

That is hard -> getting typescript tests to work. That is now easily solved by Bun.

I would recommend not testing components, reasons:

  • high complexity, have to change coding style, etc
  • hard to setup tests
  • do not really test visual stuff, ex: like is this button in view or somewhere outside?
  • instead just skip component tests and use e2e tests with playwright instead, easily generated with a multitude of visual recording tools
  • playwright even allows for visual diffs, ex.like why is this button suddenly red instead of blue
Collapse
 
rahulladumor profile image
Rahul Ladumor

Hey there! Thanks for taking the time to share such a detailed perspective! 😄

You've raised a lot of great points. The simplicity of the test and expect methods is indeed a blessing for those getting into unit testing, particularly with Jest in a Node.js environment. It really helps to keep the cognitive load low.

On Writing Logic in Functions

Absolutely agree! Keeping your logic inside pure functions makes it way easier to test. This aligns well with SOLID principles, particularly the Single Responsibility Principle. The more modular your code, the easier it is to test, understand, and maintain.

On Mocking

Your point on mocking is thought-provoking. In a perfect world, we'd minimize the need for mocks, but sometimes with complex systems—especially in a cloud environment like AWS—you might need to mock services to isolate the unit of work. However, I do agree that excessive mocking could slow down the feedback loop and introduce complexity.

On Time Investment

I couldn't agree more. The time spent writing tests can often offset the time you'd otherwise spend debugging. It's all about paying it forward.

On TypeScript Tests

Thanks for the shout-out to Bun for TypeScript testing! It's always good to have easier solutions to complicated problems.

On Component Testing

Interesting viewpoint on skipping component tests. While component tests have their limitations, I feel they still add some value, especially for larger teams and projects. But yes, E2E tests with tools like Playwright can cover a lot of what component tests do, plus give us visual diffs.

I really appreciate the insights you've provided. It definitely adds another layer to the discussion, and I'm sure others will find it valuable too! 🙌

So what are your thoughts on balancing unit tests with integration tests and E2E tests in a CI/CD pipeline, especially when using DevOps tools?

Cheers! 🚀

Collapse
 
adaptive-shield-matrix profile image
Adaptive Shield Matrix

E2E are integration tests.
No other way can you test integration, E2E-Tests are the only way.

Even with Next.js (Frontend and Backend in one) you still depend on and call external services, db.
If any of your external services (or db) does not behave how you expected it (api changes or anything else) -> your entire app does not work. Examples: db, network, email, payment, not enough resources on the vps, etc.

Testing-Balance

  • Obviously you do not need to write any tests if you are prototyping and just want to show something. The moment you ship something to users:
  • You do maximum unit tests.
  • You do e2e tests for most primary/common use cases, because debugging and maintaining e2e tests is more expensive and time consuming. If your website or web app changes offen (user workflow) -> you should have only a small amount of e2e tests, because then you have to update the tests offen as well -> that hurts greatly if you have many of them. If your user workflow changes less often then you can go all out and "lock everything down" or "cement" into tests.

The Balance highly depends on the competence of your devs/team.

  • If you have a team of many inexperienced juniors -> then you have to obviously increase your test to be confident nothing breaks down then it hits production. This leads to a more defensive development style.
  • If you have team members, who are highly competent -> then you can skip many ci/cd/devops tools completely, since everyone of them should/would have run and tested it locally. Errors might still happen, but you know that they will be resolved quickly. This leads to a more offensive development style.
Thread Thread
 
rahulladumor profile image
Rahul Ladumor

E2E as Integration Tests

I couldn't agree more. E2E tests are essentially the ultimate integration tests. They simulate real-world scenarios, making sure that all the cogs in your system mesh perfectly. This is crucial when you're relying on external services or databases; things can break unpredictably when dependencies change, just as you've pointed out.

Testing Balance

Your breakdown on when and how much to test is on point. Prototyping doesn't require exhaustive tests, but the game changes when you're shipping to users.

  • Unit Tests: You rightly emphasized their importance for capturing the maximum number of edge cases.

  • E2E Tests: They are resource-heavy but indispensable for primary/common use-cases. Totally agree that their quantity should be proportional to how often the user workflow changes.

Team Competence and Testing

Love this point. The competence level of the dev team plays a huge role in determining the testing strategy. Juniors may require a safety net of extensive testing, while a team of seasoned devs might be able to get by with lighter testing, as they're likely running extensive tests locally.

You summed it up perfectly; it's a balance that's influenced by multiple factors including the competency of the team, the stability of the user workflow, and the nature of your application's dependencies.

In light of your insights, what tools and frameworks do you recommend for optimizing this testing balance, especially when you're in an agile environment where things can change rapidly?

Thanks again for enriching this discussion! 🚀

Thread Thread
 
adaptive-shield-matrix profile image
Adaptive Shield Matrix • Edited
  • I like the Buns build-in test runner. I have ported all my tests from jest and I'm completely happy with it.
  • I test and develop all my components visually with a specially build page (similar to storybook) there I can toggle/circle all its states with mock data and debug it.
  • E2E tests - with playwright.
Thread Thread
 
rahulladumor profile image
Rahul Ladumor

Hey, thanks for sharing your toolkit! 🛠️

It's great to hear that you're loving Bun's built-in test runner and that it's meeting all your needs after migrating from Jest. That's a significant move and speaks volumes about its capabilities!

Your approach to component testing is super interesting, kind of like a homegrown Storybook. Testing components with actual visual scenarios and mock data is a solid practice, for sure.

As for E2E, Playwright is an excellent choice. It's incredibly powerful for automating browser tasks and conducting visual tests.

Just a heads-up, though: Bun might not be the best fit for everyone, especially those heavily invested in serverless architectures. But for those who can use it, sounds like it's working wonders for you.

Given your extensive experience, do you have any tips on managing test data? How do you usually go about setting up, tearing down, or updating mock data in your tests?

Cheers! 🚀

Thread Thread
 
adaptive-shield-matrix profile image
Adaptive Shield Matrix

Since you mention the usage of aws and serverless.

I really like the idea of serverless, but all the ecosystem tools surrounding are not where yet - Its the reason I'm avoiding it. Especially for the reasons you mention

  • its hard (or cumbersome) to test
  • its hard (or cumbersome) to debug
  • its hard (or cumbersome) to get logs
  • its hard (or impossible) to work completely locally without an internet connection
  • There are specialized tools/open source projects to mock/simulate most of aws infrastructure -> so you would be best quipped to use them, I think.

Using local-first

  • I have done everything to avoid having to depend on any services that I can't reproduce locally without an internet connection.
  • I use locally only a single sqlite dababase. Which is embedded and therefore can't be offline separately from the web app. If scaling becomes an issue in prod (currently not) I intent to use turso.tech/ -> the same sqlite-api used in a globally distributed edge environment. I skipped postgres (even if it is the superior dababase) for this very same reason -> spinning the db up and down, generating, cleaning test data is much more cumbersome. I do not use a aws/gcp/azure prodvided custom db because I can't run it locally.
  • I'm doing everything to allow the fastest possible way for me to develop. I will not use something if it slows down my development-feedback cycle of writing code and seeing if it right (visual tests or unit tests).

You obviously have a different tech stack.
I do not think I can help you without more Information / examples of how you or others have done it.
I do not have to set-up, tearing down or mock anything in my tech stack.

Thread Thread
 
rahulladumor profile image
Rahul Ladumor

Hey, thanks for the detailed reply! Your perspective on avoiding serverless for its current ecosystem challenges is understandable. It's definitely an evolving space, and yes, the challenges in testing, debugging, and local development can't be ignored.

On Local Development

Your approach to local-first development is quite smart. Using SQLite for its simplicity and efficiency is an excellent way to maintain speed in your dev cycle. Your point about avoiding cloud-specific databases because they can't be run locally resonates well with the idea of having a seamless dev environment.

Development Feedback Cycle

Couldn't agree more about the importance of a fast development-feedback cycle. It's clear you've optimized for this, given your choice of tools and practices.

Specialized Tools for AWS Mocking

There are indeed specialized tools to mock AWS services, like LocalStack or the Serverless Framework's offline plugins. But I get why you'd prefer a stack that doesn't need such workarounds.

Your comment brings up an important angle on tech stack choices; they really are influenced by what each of us is optimizing for—be it fast feedback cycles, scalability, or something else.

Given your approach, how do you handle scenarios that may require more complex integrations, especially when scaling up? Would love to hear your thoughts!

Thanks for enriching the conversation! 🚀

Thread Thread
 
adaptive-shield-matrix profile image
Adaptive Shield Matrix

That kind of integrations?
Internal ones (managed by same company of different teams) or external ones?

Thread Thread
 
rahulladumor profile image
Rahul Ladumor

Ah, good question! I was thinking more along the lines of external integrations—like third-party services or APIs that you might not be able to run locally. How do you handle those, especially considering your emphasis on local-first development?

Looking forward to your insights! 🚀

Thread Thread
 
adaptive-shield-matrix profile image
Adaptive Shield Matrix
  • auth -> can be skipped, just create the test user directly
  • email using a third party provider -> reroute to your own mail address or show a desktop notification instead github.com/mikaelbr/node-notifier
  • s3 compatible file storage -> replace/rerouted to local file access
  • pdfs generated with locally run github.com/pagedjs/pagedjs rendered from html/web version
  • real time -> using websockets, integrated into Bun

Have I missed something?

Thread Thread
 
rahulladumor profile image
Rahul Ladumor

That's a really neat breakdown of how you handle external integrations! 😎

  • Auth: Directly creating a test user makes sense for bypassing external auth services during testing.

  • Email: Rerouting to your own address or using desktop notifications is a solid workaround.

  • File Storage: Using local file access as a stand-in for S3 or similar services is a pragmatic approach.

  • PDF Generation: Local PDF generation libraries like Paged.js can indeed mimic the functionality of many external services.

  • Real-time: Bundling real-time capabilities via websockets directly into Bun shows a well-integrated solution.

You've clearly put thought into how to maintain a fast development-feedback cycle while still enabling complex functionalities. It seems like you've got most bases covered. 💯

Given that you're able to bypass or mock most external dependencies for local development, do you ever find that this approach leads to discrepancies when you deploy to a real-world environment?

Cheers! 🚀

Collapse
 
artxe2 profile image
Yeom suyun

coding without unit testing is like playing Russian roulette with your project.

I agree with you to some extent, even though it is a bit extreme.
However, I am disappointed that vitest is not included in the recommended test libraries.

Collapse
 
rahulladumor profile image
Rahul Ladumor

Hi @artxe2 Firstly, I appreciate you taking the time to read the article and sharing your perspective. I agree that everyone has their own experiences and preferences when it comes to development practices.

Regarding vitest, you're absolutely right! It's indeed a valuable testing library, and I'm grateful you brought it to my attention. I focused on some of the more widely known libraries in the article, but I acknowledge that there are many fantastic tools out there, including vitest. I'll definitely consider diving deeper into it and perhaps even feature it in a future piece. Your feedback helps in making the content better and more inclusive for everyone. Thanks again! 🙌

Warm regards,
Mr. Rahul

Collapse
 
tracygjg profile image
Tracy Gilmore

Hi Rahul,
I very much agree with your post. I think Unit test is actually less about testing and more about proving developer intent. That is why I wrote a post on this theme. I hope you get chance to read it and please give me your thoughts.

I heard it said, "If your are refactoring code without unit tests your are not refactoring, you are just changing code" and IMO you get what you deserve!
Regards, Tracy

Collapse
 
rahulladumor profile image
Rahul Ladumor • Edited

Hi @tracygjg ,

Thank you for your comment! It's always gratifying to meet someone who shares similar viewpoints. I completely agree—unit tests indeed go beyond just "testing"; they're about affirming that the code does what it's intended to do.

Best regards,
Mr. Rahul

Collapse
 
nhathuoc115com profile image
Nhà thuốc 115 thuoc115.com

Your source code is really good.

Collapse
 
fetidd profile image
Ben Jones

Unsung? Seriously?

Some comments have been hidden by the post's author - find out more