Unit testing is one of the most vital parts of your entire test suite. We have been putting a lot of focus on this through many of our articles and our overall operating methods. You can know everything about this massive part of the testing pyramid from the article “What Is Unit Testing? Definition, Basics, and Types”. However, we are providing a brief introduction here for your better understanding.
Unit testing is the process of testing the smallest bit of your code (also called a unit) in isolation. It is the first, the largest, and a truly important subset of the testing pyramid, the universally followed strategy for efficient software testing. Its primary duty is testing the units of the source code to check if there is any presence of the common issue where the working of every other unit gets affected whenever any change is made to an individual unit. Though modern unit testing frameworks allow one to write test codes in the same language in which the source code is written, the present world is preferring the amazing no-code testing tools like PreFlight as these enable every member of their teams to take an active part in the testing process irrespective of their coding knowledge.
Now, it goes without saying that similar to every other framework used for its specific purposes, a Unit testing framework is developed with an objective to bring ease to unit testing through the creation of test fixtures. Those test fixtures are nothing but .NET classes with special attributes of being capable of being picked up by a test runner.
However, you can perform unit tests without such a framework but in that case, the testing process becomes more cumbersome and loses most of its advantages of being automated. It is your task to understand your needs and choose the most suitable unit testing framework out of the several ones available there. Each one of them has a different set of advantages and disadvantages so the best way to choose one out of many is to analyze what your requirements are and how expert your testing team is to operate the framework. On the other hand, efficient no-code test automation tools like Preflight absolutely demands no expertise from the person using them. Anyone from any team (be it sales, marketing, finance, etc.) can easily create and execute efficient test cases without dealing with even a line of code.
In simple language, mocking is imitating or making a replica of something. But, what is its use in unit testing? Well, unit testing is all about testing an individual unit in isolation but sometimes the units have external dependencies. In such cases, those dependencies are replaced by controlled replacement objects that imitate or simulate the behavior of the real ones. This process of replicating the real environment having actual external dependencies is called mocking. And, now we will dive deeper into it in the further sections.
Till now, we got a generalized idea about mocking but based on various parameters, it is further categorized into these three types that are mentioned below.
A stub is an object that returns a specific result based on a specific set of inputs and the best part about using it is that it normally doesn't respond to anything outside the programmed boundaries/limitations of the test. If any unexpected input is provided to it, it generally ignores that or returns the same thing every time. The primary objective of this one is to set up the expected state for the test.
Though using stubs can be really helpful during the early development phase of a system, they bring a massive flaw in the operation. Every test needs multiple stubs of its own to test the different states. As a result, it becomes a repetitive task and a lot of boilerplate code gets involved. To avoid such issues, instead of using only stubs for mocking, an approach of pairing them with test doubles is followed. Stubs can run without a framework but using mocking frameworks speeds up the process of building stubs.
Mocks are generally described as pre-programmed objects with expectations that form a specification of the calls they are expected to receive. However, for better understanding, these are called sophisticated versions of stubs because they return values like stubs but can be programmed with expectations in terms of how many times each method should be called, in the specified order, and with specified data. They can be described as a replacement object for the dependency that has certain expectations like validating a sub-method that has been called a certain number of times or the arguments that are passed down in a certain way.
Most of the popular programming languages have a lot of mocking frameworks. You can even find them built into the unit test packages of some languages. They are so much preferred because they enable you to write unit tests easily and provide an environment with good unit testing practices.
Mocks have a crucial difference from other test doubles. They do behavioral verification, whereas other test doubles do state verification. The advantage of behavioral verification is that you become able to test the implementation of the system under test as you expect. On the other hand, state verification has no feature of testing the implementation of the system. It just validates the inputs and the outputs to the system.
However, this direct association with the implementation of the system becomes a minus point for behavioral verification as well as for mocks. The primary criteria that unit tests need to fulfill is that no matter what changes are being made to any unit at any point in time, other unit tests must successfully pass. But, if the tests need to be updated for every change in the behavior of the method, there will be a high chance that bugs will get introduced.
Let's understand this phenomenon with an example. Suppose one function of your web page needs data from 4 different databases to operate and you have one test that ensures the smooth receiving of data from 4 databases. After a while, some changes are made such as the API being updated and now it requires data from only 2 databases to operate. But, your test keeps looking for data from 4 databases. To resolve this problem, you need to update your test. As a result, more possibilities of bugs get introduced.
Such problems are mostly caused by the behavioral verification phenomenon that minutely checks the implementation of the code. However, the use of mocking in unit testing that pushes you to write such tests is one of the biggest reasons behind it.
The objects that replace the actual code by implementing the same interface but without interacting with other objects are called “Fakes”. These have actual working implementations but the shortcuts taken by them make it hard to produce them. Fakes are hard-coded objects that return fixed results. That's why if you try to test different use cases, you must introduce numerous fakes. This phenomenon causes a common problem that if you modify an interface, all fakes that are implementing this interface will also need to be modified.
Let's try to understand it with a common example. Fakes are commonly used in memory databases. They need to save data somewhere between application runs. And, for the smooth execution of that function, you need to design a fake implementation of your database APIs that store all data in memory. These fakes help in maintaining abstraction and keep the tests fast.
Though creating fakes involves more time than other test doubles, it is a more preferred choice for you. Fakes are full implementations that have their own suites of unit tests. Hence, using fakes boosts your confidence. You have the internal satisfaction that your test doubles do not have bugs as they are thoroughly tested even before you use them as a downstream dependency.
Another reason to prefer fakes is that they also encourage the creation of testable designs. But, whereas mocks need frameworks to write their codes, fakes can be easily written like any other implementation class. Though it is possible to include fakes in the test code only, many times they end up being included in the product code. Being held to the same standard as full unit tests, fakes can sometimes even start off in the product code. While writing a library or an API, including the fakes in the product code exempts developers from writing their own mock implementations. And, the best part is that using fakes with such efficient strategies makes the code further reusable.
You can clearly see that out of the three main types of test doubles or fake test environments, stubs and mocks are the two most popular ones. But, which one among them is the best suited for you? Well, both stand higher on different grounds. The following differences between them can really help you make your choice according to your requirements.
For the best results using test doubles, you need to know when you should use which one. The suitable scenarios to use a stub is mentioned below.
Mocks also have some particular types of use cases that are best handled by them. Let's check them out.
Though for small to medium codebases, most developers prefer writing the test code by themselves for better control over their code, for testing larger applications, mocking frameworks help a lot by providing access to large libraries and other efficient features. There are numerous mocking frameworks available in the market so you have a lot of options to choose from. Here is the general categorization of mocking frameworks so that you can decide which one will properly fulfill your requirements.
The name clearly shows that these frameworks are all about using duplicates instead of originals. Any object that is used as the replacement of an original object, is called a proxy. Similarly, in this method, when the proxy is called, it can decide from the following options how to respond to that call.
If the proxy handles all method invocations itself, there will be no necessity for an instance of an interface/class. Some popular proxy-based mocking frameworks are EasyMock, JMock, Mockito, etc. Let's understand their operation with an example.
Here, this code works for creating a proxy for the “Welcome” class.
Though proxies are amazing mocking frameworks, they have a few limitations too. Along with the inability to build a proxy for a final class, you cannot also intercept -
As the name suggests, the primary step in this method is telling the class loader to remap the reference to the class file it loads. Let's get a clearer idea through an example.
Suppose you have a Hello class with the corresponding .class file named Hello.class. Now, you want to mock it to use MyMock as a replacement. To do that using this type of framework, in the classloader, you have to remap the reference from Hello.class to MyMock.class.
This type of framework provides you with the ability to mock objects that are created by using the new operator. As a result, you will feel more power than you feel while using proxy-based ones.
Some popular classloader remapping-based mocking frameworks are jMockit, PowerMock, etc. And, the best part about using these amazing frameworks is that you can get rid of the limitations that you experience with the proxy-based frameworks.
After all these discussions, you can clearly see how important it is to use test doubles. Mocking is a truly necessary process to maintain a healthy test suite. However, while using mocking frameworks or test doubles, you must pay attention to the consequences of using a mocking framework from the beginning. Using a mocking framework is really helpful but if you feel like using it has become essential, know that your code itself is not abstracted enough.
A better practice is starting with a mocking framework and eventually creating more fake implementations by yourself. It leads you to have a healthier code and you will not end up wasting your time relying too much on mocks or testing the unnecessary things.
However, the modern world wants to achieve the best results within the least amount of time. That is only possible by using efficient no-code testing tools and Preflight is one of the most renowned ones. Our simple yet highly effective browser extension lets every member of your product team equally contribute to testing every kind of tech product. No matter if anyone has coding knowledge or not, he/she can create and execute test cases with just some clicks. You just need to start experiencing it by booking a demo.