Class Mocks
Understand how to effectively mock the interfaces and behaviors of a class.
Why do we mock classes?
There are three main reasons to mock classes. The first is the separation of concerns. If the code we are testing depends on a class, we don’t want this dependency to cause the passing or failing of our test. Mocking allows us to ensure the class is behaving as expected and that only the actual code at hand is being tested.
The second is our testing environment. Classes might interact with a database or make calls to external services. One way to handle this is through class mocks.
Last, gaining deeper insights into how our classes are calling methods can be very useful in testing our code. Mocking classes allows us to make assertions on how methods are called, what arguments they have, the number of times they are called, and other parameters.
How do we mock a class?
We have four options for mocking our classes. The option we choose depends on our testing needs. For instance, testing something higher level in an application might only need a mock of the class itself, while code that depends on a class likely requires more intricate mocking. Our options for mocking are leveraging Jest’s handy automatic mocking, manually mocking the class, using jest.mock()
, and using mockImplementation()
.
Automatic mocking
Automatic mocking is a solid option for that high level scenario above. Automatic mocking mocks the class, but it doesn’t actually do anything with the implementation details of the class.
Manual mocking
Manual mocks, by convention, live in a __mocks__
directory and have a file name that corresponds to the file name of the class being mocked. They export the mock for import into our test files. This method for mocking allows us to then mock the implementation of the class when we need to.
The jest.mock()
function
To use this function, we need to pass in the path to the class as the first argument and a function called a module factory parameter for the second argument. The module factory returns a higher order function that is then used as the class constructor in the testing environment.
The mockImplementation()
function
Last, mockImplementation()
and mockImplementationOnce()
allow for the mocking of the actual implementation of the class. Like with jest.mock()
, we are ultimately returning something that matches the interface of the class we are mocking.
Leveraging mocked classes
Once we have the mocked class, we need the ability to effectively work with it in order to make our test development a smooth process. As mentioned above, interacting with these mocks depends largely on the needs of the test suite. If no specific implementation mocking is needed, simple automatic mocks or the importing of a manual mock may suffice. However, for more nuanced needs, we need to dig a bit deeper.
Mocking the methods of a class
We can mock the specific methods of a class when we want tight control over their execution and return, or when we need to be able to see how they run, i.e., when we need to spy on them. It’s important to note that private methods cannot be mocked. Usually, this is fine. Since private methods are not surfaced but rather called through other public methods, testing them through these public methods is a good way to approach their testing. They may need a bit of extra attention to ensure they are fully tested, though.
Mocking getters, setters, and static
Getters, setters, and static can easily be mocked as well, which is quite useful when testing the expected state of a class. For instance, we may want to ensure that a class method ultimately calls a setter method. Mocking the setter allows us to spy on it and make this assertion.
Class mocking in action
In the example below, we have a class, Author
, with examples of the approaches discussed above. This class has access to another class, Book
, which we are still in the process of developing. We can see in book.js
that the class has a number of methods that describe the class’s expected functionality but are not yet implemented. Because these are not yet implemented, we need to mock this entire class. We maintain this mock in the __mocks__
directory, and we mock the implementation of its methods as needed.
Get hands-on with 1400+ tech skills courses.