The unit test mock and related terms (Test Double): a note and examples to martin old article

Ref: https://www.martinfowler.com/articles/mocksArentStubs.html

http://xunitpatterns.com/Test%20Double.html

https://github.com/kensipe/spock-mocks-nfjs

http://nilhcem.com/FakeSMTP/.

It is hard to not write unit test for your programmer job. Of course, you can quit if you do not like to write and your boss requests. But, anyway, it is better to write to ensure your code is safe to run, and your job is safe to stay.

Most of your unit test main object, i.e. system under test (SUT), are relied on dependency object.  For example, you have Order class and Warehouse class. When you fill the order in Order.fill method, you have to decrease the quantity from the Warehouse object with Warehouse.remove method. In this context, when you conduct the unit test o Order class, you have the Warehouse as dependency object. Also, we will send email when the order is filled either success or failure. So, mail service is also the dependency object for our order SUT object.

But, sometimes, you do not like to test the dependency object for some reasons as below,

  1. The dependency object is not implemented by the lazy team member.
  2. The dependency object is too hard to establish the environment, for example, you may need to create a mongodb cluster with 10 nodes to get your dependency object work.
  3.  The dependency object is too slow to call, for example, the dependency object is lived in US and your code lives in England.

Must you have those real dependency object for your SUT? No. You can use a-not-real dependency object for your SUT test.

As Martin has written those good article, I will just provide examples to illustrate it. But, most important in his article is the below quoted paragraph about the five terms related to unit test dependency objects.  Meszaros in the xunitpatterns coins them as Test Double.

  • Dummy objects are passed around but never actually used. Usually they are just used to fill parameter lists.
  • Fake objects actually have working implementations, but usually take some shortcut which makes them not suitable for production (an in memory database is a good example).
  • Stubs provide canned answers to calls made during the test, usually not responding at all to anything outside what’s programmed in for the test.
  • Spies are stubs that also record some information based on how they were called. One form of this might be an email service that records how many messages it was sent.
  • Mocks are what we are talking about here: objects pre-programmed with expectations which form a specification of the calls they are expected to receive.

It is easy to understand Dummy, Fake. For Mock and Stub, here is the difference:

  1. Mocks doubles are used for behavior test, i.e. test step by step in your SUT testing method. Other doubles are using for
  2. Stub are used for state test, i.e. test whether the property variable (the state in an object) are correct.

Back to the test, when will we use real dependency object to test and when to use double to test? The rule is simple depended how is the relationship between SUT object and the dependency object.  If it is an easy cooperation, for example the order and warehouse, you can choose either one up to your preference. However, if it is an awkward cooperation, such as order and mail service (you need to send mail to users after order is filled.), you may consider using Mock double or Stub double.

OK. That is end for the note, below will be the source code to illustrate for all those doubles.

  1. cod pharmacy soma The Order Fill without mail function and Fill with mail function.

public class Order {

public Order(String item, int quantity,IMailService mail) {

    if(mail==null) throw new IllegalArgumentException(“IMailService param cannot be null.”);

        this.item = item;

        this.quantity = quantity;

        this.mail = mail;

         

    }

public void fill(Warehouse warehouse) {

        if (warehouse.hasInventory(item, quantity)) {

            warehouse.remove(item, quantity);

            filled = true;

        }

       }

    public boolean isFilled() {

        return filled;

    }

   

    public void fillandMail(Warehouse warehouse) {

            fill(warehouse);

            mail.sent(this);

}

         }

  1. The Test without Doubles, using real dependency objects.

public class OrderImplExampleTest extends TestCase {

    private static String TALISKER = “Talisker”;

    private static String HIGHLAND_PARK = “Highland Park”;

private Warehouse warehouse = new WarehouseImpl();

private IMailService realMailSMTPService

    protected void setUp() throws Exception {

        warehouse.add(TALISKER, 50);

        warehouse.add(HIGHLAND_PARK, 25);

 realMailSMTPService=new realMailSMTPService();

    }

    public void testOrderIsFilledIfEnoughInWarehouse() {

        Order order = new Order(TALISKER, 50, realMailSMTPService);

        order.fill(warehouse);

        assertTrue(order.isFilled());

        assertEquals(0, warehouse.getInventory(TALISKER));

    }

    public void testOrderDoesNotRemoveIfNotEnough() {

        Order order = new Order(TALISKER, 51, realMailSMTPService);

        order.fill(warehouse);

        assertFalse(order.isFilled());

        assertEquals(50, warehouse.getInventory(TALISKER));

}

    public void testOrderIsFilledIfEnoughInWarehouseAndMail() {

            

        Order order = new Order(TALISKER, 50,realMailSMTPService);

        order.fillandMail(warehouse);

        assertTrue(order.isFilled());

        assertEquals(0, warehouse.getInventory(TALISKER));

        assertTrue(1.realMailSMTPService.count());

    }

}

  1. The Test with Dummy for mailService

As you can see the implementation in the Order.fill, the IMailService is never used. The Order construction does not allow null. If the real mail service class is not yet implemented, we should create a dummy mail service double class for the test.  In the dummy class below, nothing is handled for the interface.

public class dummyMail implements IMailService {

        @Override

        public boolean sent(Order order) {                return true;        }

        @Override

        public int count() {                return 0;        }

}

And here, it is the test with dummy.    

public void testOrderIsFilledIfEnoughInWarehouseWithDummyMail() {

        Order order = new Order(TALISKER, 50, new dummyMail());

        order.fill(warehouse);

        assertTrue(order.isFilled());

        assertEquals(0, warehouse.getInventory(TALISKER));

    }

  1. The Test with Mock for warehouse

The implementation in the Order.fill requires to check warehouse inventory and decrease it if order filled with enough inventory.

The inventory check and inventory decrease should be tested in the warehouse object. So, in our test for order.fill, we can ignore to use the real warehouse object to test, instead we can use a mock to only test the steps involved warehouse in the order.fill procedure. We call it as behavior test, which test the procedure is correct or not.

    public void testFillingRemovesInventoryIfInStock() {

        //setup – data

        Order order = new Order(TALISKER, 50,new dummyMail());

        Mock warehouseMock = mock(Warehouse.class);

        //setup – expectations

        warehouseMock.expects(once()).method(“hasInventory”)

                .with(eq(TALISKER), eq(50))

                .will(returnValue(true));

        warehouseMock.expects(once()).method(“remove”)

                .with(eq(TALISKER), eq(50))

                .after(“hasInventory”);

        //exercise

        order.fill((Warehouse) warehouseMock.proxy());

        //verify

        warehouseMock.verify();

        assertTrue(order.isFilled());

    }

The two warehouseMock.expects sentences are the similar two steps (warehouse.hasInventory(item, quantity) and warehouse.remove(item, quantity)) in the order.fill function.

The first warehouseMock.expects with return true is to mock that inventory is enough, so that order.fill can continue the remove mock.

  1. The Test with Stub for Mail Service

The Order.FillAndMail function requires to send mail after filled. In the real mail service test above, it has to check whether mail sends successfully or not with the mail.count method. However as Mock can only test whether the procedure is experienced or not, it does not record how many mail is sent. In this case, we should use mail stub if the real mail service is not ready.

The mail stub will provides state verification (i.e. check how many mail sent) plus the behavior test (i.e. check the function step by step.)

public class MailStub implements IMailService {

        private List<Order> messages = new ArrayList<Order>();

        @Override

        public boolean sent(Order order) {

                 messages.add(order);

                return true;

        }

        @Override

        public int count() {

                return messages.size();

        }

}

Here is the test stub using in the test for fill and mail function.

    public void testOrderIsFilledIfEnoughInWarehouseAndMail_Stubs() {

            IMailService mailStub=new MailStub();

        Order order = new Order(TALISKER, 50,mailStub);

        order.fillandMail(warehouse);

        assertTrue(order.isFilled());

        assertEquals(0, warehouse.getInventory(TALISKER));

        assertTrue(1.mailStub.count());

}

  1. The Test with Stub Spy for Mail Service

You may like to get more information beyond the IMailService from the stubs. For example, you want to check how many message with order fill success in the mail stub. Now, the mail stub turns to a mail stub spy. Here is the code. The difference is to use MailStub mailStub=new MailStub() to get the mailStub.countSuccessFill() method later. (if you use IMailService mailStub=new MailStub(), you can’t use countSuccessFill.)

public class MailStub implements IMailService {

        private List<Order> messages = new ArrayList<Order>();

        @Override

        public boolean sent(Order order) {

                 messages.add(order);

                return true;

        }

        @Override

        public int count() {

                return messages.size();

        }

        public long countSuccessFill(){

                return messages.stream().filter(o->o.isFilled()).count();

        }        

}

    public void testOrderIsFilledIfEnoughInWarehouseAndMail_Stubs() {

            MailStub mailStub=new MailStub();

        Order order = new Order(TALISKER, 50,mailStub);

        order.fillandMail(warehouse);

        assertTrue(order.isFilled());

        assertEquals(0, warehouse.getInventory(TALISKER));

        assertTrue(1.mailStub.count());

assertTrue(1.mailStub.countSuccessFill());

}

  1. The Test with Mock for Mail Service

Of course, you can use Mock to test the fill and mail behavior, here is the mock test

  public void testOrderSendsMailIffilled() {

    Order order = new Order(TALISKER, 51);

    Mock warehouse = mock(Warehouse.class);

    Mock mailer = mock(MailService.class);

    order.setMailer((MailService) mailer.proxy());

    mailer.expects(once()).method(“send”);

    warehouse.expects(once()).method(“hasInventory”)

      .withAnyArguments();

order.fillandMail((Warehouse) warehouse.proxy());

warehouse.verify();

  }

}

  1. The Test with Fake double for Fake mail Service

Our simple example is not a good one to demonstrate the fake mail service. You may reference to http://nilhcem.com/FakeSMTP/.

The purpose of the fake mail service is allowed you to store and view your mails in the local/specific machine. The fake mail service may have full set service of the real mail service. So that you do not need to send mail and test the external real mail service.  Below is an example of local mail smtp service from the FakeSMTP jar. You can start the fake mail with simple command java -jar fakeSMTP.jar. Then copy your real mail implementation as fake mail implementation with server and port address update. Now you are ready to use your fake mail in your test case.

Screenshot






Leave a Reply