建造者模式Builder是一種常用的設(shè)計(jì)模式,用于構(gòu)建不同的產(chǎn)品類。
如有以下的Builder
package com.github.mockito.builder;
class Builder{
private String name;
private String address;
public Builder setName(String name){
this.name = name;
return this;
}
public Builder setAddress(String address){
this.address = address;
return this;
}
public String sayHello(){
return "hello";
}
}
以下是一個(gè)調(diào)用的場(chǎng)景
package com.github.mockito.builder;
public class BuilderDemo {
public Builder builder;
public String sayHello(){
return builder.setName("name").setAddress("address").sayHello();
}
}
建造者模式引人注目的是它標(biāo)志性的鏈?zhǔn)椒椒ㄕ{(diào)用(Fluent API)。
不過(guò)它這個(gè)也給單元測(cè)試造成了一定的麻煩。 先看這個(gè)案例
package com.github.mockito.builder;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
public class BuilderDemoLegacyTest {
@Mock
Builder builder;
@InjectMocks
BuilderDemo builderDemo;
@BeforeEach
public void setUp() {
MockitoAnnotations.initMocks(this);
}
@Test
public void testSayHelloShouldFail() {
when(builder.sayHello()).thenReturn("hi");
assertThat(builderDemo.sayHello()).isEqualTo("hi");
}
}
這個(gè)用例跑失敗了
失敗的癥狀是拋出了空指針異常,根本原因是沒有正確的測(cè)試樁可供使用,因?yàn)樵谡{(diào)用sayHello時(shí),mock的builder沒有被成功匹配。
看來(lái)得根據(jù)代碼依次來(lái)進(jìn)行打樁。如下例,
@Test
public void testSayHelloLegacy() {
when(builder.setName("name")).thenReturn(builder);
when(builder.setAddress("address")).thenReturn(builder);
when(builder.sayHello()).thenReturn("hi");
assertThat(builderDemo.sayHello()).isEqualTo("hi");
}
這回跑通過(guò)了
不過(guò)問題來(lái)了,為了能匹配測(cè)試樁,需要逐個(gè)對(duì)鏈?zhǔn)秸{(diào)用中的各個(gè)中間返回對(duì)象進(jìn)行打樁。如果只是像這個(gè)案例的話,也就算了,如果碰到更為復(fù)雜的鏈?zhǔn)椒椒ㄕ{(diào)用,打樁的代碼就會(huì)一大片。
還好Mockito從2.0開始為這個(gè)問題提供了一個(gè)優(yōu)雅的解決方法。
RETURNS_SELF(new TriesToReturnSelf())
來(lái)看一下使用之后的案例
package com.github.mockito.builder;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Answers.RETURNS_SELF;
import static org.mockito.Mockito.when;
public class BuilderDemoTest {
@Mock(answer = RETURNS_SELF)
Builder builder;
@InjectMocks
BuilderDemo builderDemo;
@BeforeEach
public void setUp() {
MockitoAnnotations.initMocks(this);
}
@Test
public void testSayHello() {
// when(builder.setName("name")).thenReturn(builder);
// when(builder.setAddress("address")).thenReturn(builder);
when(builder.sayHello()).thenReturn("hi");
assertThat(builderDemo.sayHello()).isEqualTo("hi");
}
}
通過(guò) @Mock(answer = RETURNS_SELF) ,告訴Mockito來(lái)返回被mock的測(cè)試樁自身,這樣就能支持鏈?zhǔn)椒椒ㄕ{(diào)用了。
以下是三個(gè)用例的執(zhí)行結(jié)果:
感興趣的讀者可以參考Mockito的Answers枚舉類,了解各種返回類型。
package org.mockito;
//
public enum Answers implements Answer<Object> {
RETURNS_DEFAULTS(new GloballyConfiguredAnswer()),
RETURNS_SMART_NULLS(new ReturnsSmartNulls()),
RETURNS_MOCKS(new ReturnsMocks()),
RETURNS_DEEP_STUBS(new ReturnsDeepStubs()),
CALLS_REAL_METHODS(new CallsRealMethods()),
RETURNS_SELF(new TriesToReturnSelf());
//
}
據(jù)說(shuō)RETURNS_DEEP_STUBS也有類似效果,感興趣的讀者可以親自試一試。
關(guān)--祝 : 軟件測(cè)試那些事