如何在Mockito中为控制器模拟服务

时间:2019-12-09 07:07:43

标签: java mockito

全部

感谢您的帮助。我对Mockito还是很陌生,如果我有一个Service类,一个控制器类(通过传递Map参数使用该服务),该如何模拟该服务方法?

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!!!");
        }
    }

服务总是返回null,我不确定是什么错误。

2 个答案:

答案 0 :(得分:1)

您的示例代码有点不正确,因为它可以正常工作而无需任何模拟。

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

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

无论如何,可能只是一个示例。

嘲讽的问题在于这一行

hservice_mock = Mockito.spy(hservice);

首先,让Mockito创建模拟(@Mock Helloworldservice hservice_mock)并将其注入到控制器(@InjectMocks Helloworldcontroller hcontroller)中,然后自己创建间谍(hservice_mock = Mockito.spy(hservice))您尝试设置期望值(when(hservice_mock.greetingSB(...)))。为间谍建立期望值需要不同的方法调用链,因此当前会发生NullPointerException(请参见Important gotcha on spying real objects!)。即使可以使用,也不会影响已经注入的模拟。

这将按预期工作:

@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!!!");
    }
}

关于测试设置的其他一些评论:

  • Helloworldcontroller依赖于Helloworldservice。与其在构造函​​数中创建实例,不如考虑使用构造函数依赖注入。您的示例代码可以工作,但是如果变得更复杂,事情就会失去控制。想想数据库访问。
  • @InjectMocks是代码气味的标志,表示要测试的代码存在更深层的问题。尽量避免。
  • assertEqualsassertEquals(h, "Hello, sombody!!!")的第一个参数是期望值,然后是实际值。听起来不是很重要,但是会影响断言违反错误消息。

让我们解决这些问题,看看我们如何改进代码。

首先,使用构造函数注入。

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);
    }
}

测试代码现在变为

@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());
    }
}

答案 1 :(得分:1)

测试类中有几个地方需要固定,如数字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!!!");
    }
}

关于完整模拟和部分模拟之间的区别,您可以参考this link

总而言之,使用模拟的基本步骤是:

  1. 创建实例(@Mock)
  2. 注入它(@InjectMocks)
  3. 创建期望(Mockito.when()。thenReturn)
  4. 调用模拟方法(或重播期望值)