Specification – adding items to the Mongo collection

We should create a method that saves data to MongoDB. After studying Jongo documentation, we discovered that there is the MongoCollection.save method, which does exactly that. It accepts any object as a method argument and transforms it (using Jackson) into JSON, which is natively used in MongoDB. The point is that after playing around with Jongo, we decided to use and, more importantly, trust this library.

We can write Mongo specifications in two ways. One more traditional and appropriate for End2End (E2E) or integration tests would be to bring up a MongoDB instance, invoke the Jongo's save method, query the database, and confirm that data has indeed been saved. It does not end here, as we would need to clean up the database before each test to always guarantee that the same state is unpolluted by the execution of previous tests. Finally, once all tests are finished executing, we might want to stop the MongoDB instance and free server resources for some other tasks.

As you might have guessed, there is quite a lot of work involved for a single test written in this way. Also, it's not only about work that needs to be invested into writing such tests. The execution time would be increased quite a lot. Running one test that communicates with a DB does not take long. Running ten tests is usually still fast. Running hundreds or thousands can take quite a lot of time. What happens when it takes a lot of time to run all unit tests? People lose patience and start dividing them into groups or give up on TDD all together. Dividing tests into groups means that we lose confidence in the fact that nothing got broken, since we are continuously testing only parts of it. Giving up on TDD... Well, that's not the objective we're trying to accomplish. However, if it takes a lot of time to run tests, it's reasonable to expect developers to not want to wait until they are finished running before they move to the next specification, and that is the point when we stop doing TDD. What is a reasonable amount of time to allow our unit tests to run? There is no one-fits-all rule that defines this; however, as a rule of thumb, if the time is longer than 10-15 seconds, we should start worrying, and dedicate time to optimizing them.

Tests should run quickly. The benefits are that the tests are used often.

If it takes a lot of time to run tests, developers will stop using them or run only a small subset related to the changes they are making. One benefit of fast tests, besides fostering their usage, is fast feedback. The sooner the problem is detected, the easier it is to fix it. Knowledge about the code that produced the problem is still fresh. If a developer has already started working on the next feature while waiting for the completion of the execution of tests, they might decide to postpone fixing the problem until that new feature is developed. On the other hand, if they drops their current work to fix the bug, time is lost in context switching.

If using live DB to run unit tests is not a good option, then what is the alternative? Mocking and spying! In our example, we know which method of a third-party library should be invoked. We also invested enough time to trust this library (besides integration tests that will be performed later on). Once we know how to use the library, we can limit our job to verifying that correct invocations of that library have been made.

Let us give it a try.

First, we should modify our existing code and convert our instantiation of the TicTacToeCollection into a spy:

import static org.mockito.Mockito.*; 
... 
@Before 
public void before() throws UnknownHostException { 
  collection = spy(new TicTacToeCollection()); 
} 

Spying on a class is called partial mocking. When applied, the class will behave exactly the same as it would if it was instantiated normally. The major difference is that we can apply partial mocking and substitute one or more methods with mocks. As a general rule, we tend to use spies mostly on classes that we're working on. We want to retain all the functionality of a class that we're writing specifications for, but with an additional option to, when needed, mock a part of it.

Now let us write the specification itself. It could be the following:

@Test
public void whenSaveMoveThenInvokeMongoCollectionSave() {
  TicTacToeBean bean = new TicTacToeBean(3, 2, 1, 'Y');
  MongoCollection mongoCollection = mock(MongoCollection.class);
  doReturn(mongoCollection).when(collection).getMongoCollection();
  
collection.saveMove(bean);
verify(mongoCollection, times(1)).save(bean); }

Static methods, such as mock, doReturn, and verify, are all from the org.mockito.Mockito class.

First, we're creating a new TicTacToeBean. There's nothing special there. Next, we are creating a mock object out of the MongoCollection. Since we already established that, when working on a unit level, we want to avoid direct communication with the DB, mocking this dependency will provide this for us. It will convert a real  class into a mocked one. For the class using mongoCollection, it'll look like a real one; however, behind the scenes, all its methods are shallow and do not actually do anything. It's like overwriting that class and replacing all the methods with empty ones:

MongoCollection mongoCollection = mock(MongoCollection.class);

Next, we're telling that a mocked mongoCollection should be returned whenever we call the getMongoCollection method of the collection spied class. In other words, we're telling our class to use a fake collection instead of the real one:

doReturn(mongoCollection).when(collection).getMongoCollection(); 

Then, we're calling the method that we are working on:

collection.saveMove(bean); 

Finally, we should verify that the correct invocation of the Jongo library is performed once:

verify(mongoCollection, times(1)).save(bean);

Let's try to implement this specification.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.3.167