简体   繁体   中英

How to mock a service for controller in mockito

All,

Thanks for help. I am pretty new to Mockito, If I have a Service class, a controller class(which using that serivce by passing in a Map param), How can I mock that service method?

Helloworldservice.java

    package service;

    import java.util.Map;

    public class Helloworldservice {
        public String greeting() {
            return "Hello, World";
        }
        public String greetingSB(Map<String, String> sb) {
            return "Hello," + sb.get("name");
        }
    }

Helloworldcontroller.java

    package controller;

    import java.util.HashMap;
    import java.util.Map;

    import service.Helloworldservice;

    public class Helloworldcontroller {
        private Helloworldservice hservice;

        public Helloworldcontroller() {
            // TODO Auto-generated constructor stub
            hservice = new Helloworldservice();
        }

        public String sayHello() {
            return hservice.greeting();
        }

        public String sayHelloSB() {
            Map<String, String> sb = new HashMap<String, String>();
            sb.put("name", "somebody");
            return hservice.greetingSB(sb);
        }
    }

HelloworldcontrollerTest.java

    package unit.controller;

    import org.junit.Assert;
    import org.junit.Before;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.mockito.InjectMocks;
    import org.mockito.Mock;
    import org.mockito.Mockito;
    import org.mockito.junit.MockitoJUnitRunner;

    import controller.Helloworldcontroller;
    import service.Helloworldservice;

    @RunWith(MockitoJUnitRunner.class)
    public class HelloworldcontrollerTest {
        @InjectMocks
        private Helloworldcontroller hcontroller;

        private Helloworldservice hservice = new Helloworldservice();
        @Mock
        private Helloworldservice hservice_mock;
        @Before
        public void setup() {
            hservice_mock = Mockito.spy(hservice);
            /**  I am not sure how to mock here for that param sb
            Mockito.when(hservice_mock.greetingSB(.......))
                    .thenReturn("Hello, somebody");

            **/
        }

        @Test
        public void testGreeting() {
            String h = hcontroller.sayHelloSB();
            Assert.assertEquals(h, "Hello, sombody!!!");
        }
    }

The serice always returns null, I am not sure what is wrong.

Your sample code is a bit off as it'll work without any mocking involved.

public class HelloworldcontrollerTest {
    private Helloworldcontroller hcontroller = new Helloworldcontroller();

    @Test
    public void testGreeting() {
        String h = hcontroller.sayHelloSB();
        Assert.assertEquals(h, "Hello,somebody");
    }
}

Anyway, it's probably just that, a sample.

The problem with mocking lies with this line

hservice_mock = Mockito.spy(hservice);

First, you let Mockito create your mock ( @Mock Helloworldservice hservice_mock ) and inject it into the controller ( @InjectMocks Helloworldcontroller hcontroller ) and then you're creating a spy on your own ( hservice_mock = Mockito.spy(hservice) ) for which you try to setup your expectations ( when(hservice_mock.greetingSB(...)) ). Setting up expectations on spies require a different method invocation chain, hence a NullPointerException would occur currently (see Important gotcha on spying real objects! ). Even if it would work, it wouldn't affect the already injected mock.

This would work as expected:

@RunWith(MockitoJUnitRunner.class)
public class HelloworldcontrollerTest {
    @InjectMocks
    private Helloworldcontroller hcontroller = new Helloworldcontroller();

    @Mock
    private Helloworldservice hservice_mock;

    @Before
    public void setup() {
        Mockito.when(hservice_mock.greetingSB(any(Map.class)))
                .thenReturn("Hello, somebody!!!");
    }

    @Test
    public void testGreeting() {
        String h = hcontroller.sayHelloSB();
        Assert.assertEquals(h, "Hello, somebody!!!");
    }
}

A few other comments on the test setup:

  • Helloworldcontroller has dependency to Helloworldservice . Instead of creating the instance in the constructor, you should consider using constructor dependency injection. Your sample code works but things go out of control should it become more complex. Think of database access.
  • @InjectMocks is a sign of code smell and indicates a deeper problem with the code to be tested. Try to avoid if possible.
  • The first argument of assertEquals as in assertEquals(h, "Hello, sombody!!!") is the expected value, then comes the actual value. Sounds not very important, but affects assertion violation error message.

Let's tackle those problems and see how we can improve the code.

First, use constructor injection.

public class Helloworldcontroller {
    private final Helloworldservice hservice;

    public Helloworldcontroller(Helloworldservice hservice) {
        this.hservice = hservice;
    }

    public String sayHello() {
        return hservice.greeting();
    }

    public String sayHelloSB() {
        Map<String, String> sb = new HashMap<String, String>();
        sb.put("name", "somebody");
        return hservice.greetingSB(sb);
    }
}

The test code becomes now

@RunWith(MockitoJUnitRunner.class)
public class HelloworldcontrollerTest {
    private Helloworldcontroller hcontroller;

    @Mock
    private Helloworldservice hservice;

    @Before
    public void setup() {
        hcontroller = new Helloworldcontroller(hservice);

        Mockito.when(hservice.greetingSB(any(Map.class)))
                .thenReturn("Hello, somebody!!!");
    }

    @Test
    public void testGreeting() {
        Assert.assertEquals("Hello, somebody!!!", hcontroller.sayHelloSB());
    }
}

There're several places need to be fixed in you test class, as listed in number 1, 2 ...

@RunWith(MockitoJUnitRunner.class)
public class HelloworldcontrollerTest {
    @InjectMocks
    private Helloworldcontroller hcontroller;

    private Helloworldservice hservice = new Helloworldservice();
    @Mock
    private Helloworldservice hservice_mock;
    @Before
    public void setup() {
    // 1. Comment out this line, because you've already created a mock instance using @Mock
//        hservice_mock = Mockito.spy(hservice);
        /**  I am not sure how to mock here for that param sb
         Mockito.when(hservice_mock.greetingSB(.......))
         .thenReturn("Hello, somebody");

         **/
    }

    @Test
    public void testGreeting() {
        // 2. This's why program output null. If you create an entire mock(not a real mock or a partial mock) object, then you should give the specific expectation for the method. 
        Mockito.when(hservice_mock.greetingSB(Mockito.any())).thenReturn("Hello world!!");
        String h = hcontroller.sayHelloSB();
        Assert.assertEquals(h, "Hello, sombody!!!");
    }
}

For the difference between entire mock and partial mock, you can refer to this link .

In summary, the basic steps to use mock is:

  1. create instance(@Mock)
  2. inject it(@InjectMocks)
  3. create expectation(Mockito.when().thenReturn)
  4. call the mock method(or replay the expectation)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM