Andular測試相關(guān)

Andular測試相關(guān)

https://blog.51cto.com/7308310/2325925?source=dra

常用斷言以及方法

  • Jasmine 提供非常豐富的API,一些常用的Matchers:
toBe() 等同 ===
toNotBe() 等同 !==
toBeDefined() 等同 !== undefined
toBeUndefined() 等同 === undefined
toBeNull() 等同 === null
toBeTruthy() 等同 !!obj
toBeFalsy() 等同 !obj
toBeLessThan() 等同 <
toBeGreaterThan() 等同 >
toEqual() 相當(dāng)于 ==
toNotEqual() 相當(dāng)于 !=
toContain() 相當(dāng)于 indexOf
toBeCloseTo() 數(shù)值比較時定義精度,先四舍五入后再比較盔几。
toHaveBeenCalled() 檢查function是否被調(diào)用過
toHaveBeenCalledWith() 檢查傳入?yún)?shù)是否被作為參數(shù)調(diào)用過
toMatch() 等同 new RegExp().test()
toNotMatch() 等同 !new RegExp().test()
toThrow() 檢查function是否會拋出一個異常

而這些API之前用 not 來表示負(fù)值的判斷。

expect(true).not.toBe(false);


angular cli使用karma進(jìn)行單元測試.

  • 使用 ng test進(jìn)行測試袱瓮。

    端對端測試的命令是 ng e2e

    常用參數(shù):
    --browsers 指定使用的瀏覽器
    --code-coverage 輸出覆蓋率報告
    --code-coverage-exclude 排除文件或路徑
    --karma-config 指定Karma配置文件
    --prod 啟用production環(huán)境
    --progress 默認(rèn)為true,將編譯進(jìn)度輸出到控制臺
    --watch 默認(rèn)為true爱咬,代碼修改后會重新運(yùn)行測試
    
  • 默認(rèn)的測試文件擴(kuò)展名為.spec.ts尺借。

  • import { TestBed, async } from '@angular/core/testing';
    import { RouterTestingModule } from '@angular/router/testing';
    import { AppComponent } from './app.component';
    import { StudyBarComponent } from './study-bar/study-bar.component';
    
    describe('AppComponent', () => {
      beforeEach(async(() => {
        TestBed.configureTestingModule({
          imports: [
            RouterTestingModule   // 有路由的測試項(xiàng)需要用到
          ],
          declarations: [
            AppComponent,
            StudyBarComponent
          ],
        }).compileComponents();
      }));
    
      it('should create the app', () => {
        const fixture = TestBed.createComponent(AppComponent);
        const app = fixture.debugElement.componentInstance;
        expect(app).toBeTruthy();
      });
    
      it(`should have as title 'my-app'`, () => {
        const fixture = TestBed.createComponent(AppComponent);
        const app = fixture.debugElement.componentInstance;
        expect(app.title).toEqual('my-app');
      });
    });
    
  • 測試結(jié)構(gòu)

    • describe函數(shù)中包含了beforeEach和it兩類函數(shù)。describe相當(dāng)于Java測試中的suite台颠,也就是測試組褐望,其中可以包含多個測試用例it。
    • 一般一個測試文件含有一個describe串前,當(dāng)然也可以有多個。
    • beforeEach相當(dāng)于Java測試中的@Before方法实蔽,每個測試用例執(zhí)行前調(diào)用一次荡碾。同樣,還有afterEach局装、beforeAll坛吁、afterAll函數(shù),afterEach在每個測試用例執(zhí)行后調(diào)用一次铐尚,beforeAll拨脉、afterAll相當(dāng)于Java測試中的@BeforeClass、@AfterClass方法宣增,每個describe執(zhí)行前后調(diào)用一次玫膀。
    • **describe和it的第一個參數(shù)是測試說明。一個it中可以包含一個或多個expect來執(zhí)行測試驗(yàn)證爹脾。 **
  • TestBed

    • TestBed.configureTestingModule()方法動態(tài)構(gòu)建TestingModule來模擬Angular @NgModule帖旨,支持@NgModule的大多數(shù)屬性箕昭。
    • 測試中需導(dǎo)入測試的組件及依賴。例如在AppComponent頁面中使用了router-outlet解阅,因此我們導(dǎo)入了RouterTestingModule來模擬RouterModule落竹。Test Module預(yù)配置了一些元素,比如BrowserModule货抄,不需導(dǎo)入述召。
    • TestBed.createComponent()方法創(chuàng)建組件實(shí)例,返回ComponentFixture蟹地。ComponentFixture是一個測試工具(test harness)积暖,用于與創(chuàng)建的組件和相應(yīng)元素進(jìn)行交互。
  • nativeElement和DebugElement

  • 示例中使用了fixture.debugElement.nativeElement锈津,也可以寫成fixture.nativeElement呀酸。實(shí)際上,fixture.nativeElement是fixture.debugElement.nativeElement的一種簡化寫法琼梆。nativeElement依賴于運(yùn)行時環(huán)境性誉,Angular依賴DebugElement抽象來支持跨平臺。Angular創(chuàng)建DebugElement tree來包裝native element茎杂,nativeElement返回平臺相關(guān)的元素對象错览。我們的測試樣例僅運(yùn)行在瀏覽器中,因此nativeElement總為HTMLElement煌往,可以使用querySelector()倾哺、querySelectorAll()方法來查詢元素。

    element.querySelector('p');
    element.querySelector('input');
    element.querySelector('.welcome');
    element.querySelectorAll('span');
    
  • detectChanges
  • createComponent() 函數(shù)不會綁定數(shù)據(jù)刽脖,必須調(diào)用fixture.detectChanges()來執(zhí)行數(shù)據(jù)綁定羞海,才能在組件元素中取得內(nèi)容:

    it('should render title in a h1 tag', () => {
      const fixture = TestBed.createComponent(AppComponent);
      fixture.detectChanges();
      const compiled = fixture.debugElement.nativeElement;
      expect(compiled.querySelector('h1').textContent).toContain('Welcome to hello!');
    });
    

    當(dāng)數(shù)據(jù)模型值改變后,也需調(diào)用fixture.detectChanges()方法:

    it('should render title in a h1 tag', () => {
      const fixture = TestBed.createComponent(AppComponent);
      const app = fixture.componentInstance;
      app.title = 'china';
      fixture.detectChanges();
      const compiled = fixture.nativeElement;
      expect(compiled.querySelector('h1').textContent).toContain('Welcome to china!');
    });
    

    可以配置自動檢測曲管,增加ComponentFixtureAutoDetect provider:

    import { ComponentFixtureAutoDetect } from '@angular/core/testing';
    ...
    TestBed.configureTestingModule({
      providers: [
        { provide: ComponentFixtureAutoDetect, useValue: true }
      ]
    });
    

    啟用自動檢測后僅需在數(shù)值改變后調(diào)用detectChanges():

    it('should display original title', () => {
      // Hooray! No `fixture.detectChanges()` needed
      expect(h1.textContent).toContain(comp.title);
    });
    
    it('should still see original title after comp.title change', () => {
      const oldTitle = comp.title;
      comp.title = 'Test Title';
      // Displayed title is old because Angular didn't hear the change :(
      expect(h1.textContent).toContain(oldTitle);
    });
    
    it('should display updated title after detectChanges', () => {
      comp.title = 'Test Title';
      fixture.detectChanges(); // detect changes explicitly
      expect(h1.textContent).toContain(comp.title);
    });
    
  • 依賴注入

    • 對簡單對象進(jìn)行測試可以用new創(chuàng)建實(shí)例:

      describe('ValueService', () => {
        let service: ValueService;
        beforeEach(() => { service = new ValueService(); });
          ...
      });
      

      不過大多數(shù)Service却邓、Component等有多個依賴項(xiàng),使用new很不方便院水。若用DI來創(chuàng)建測試對象腊徙,當(dāng)依賴其他服務(wù)時,DI會找到或創(chuàng)建依賴的服務(wù)檬某。要測試某個對象撬腾,在configureTestingModule中配置測試對象本身及依賴項(xiàng),然后調(diào)用TestBed.get()注入測試對象:

      beforeEach(() => {
        TestBed.configureTestingModule({ providers: [ValueService] });
        service = TestBed.get(ValueService);
      });
      

      單元測試的原則之一:僅對要測試對象本身進(jìn)行測試恢恼,而不對其依賴項(xiàng)進(jìn)行測試民傻,依賴項(xiàng)通過mock方式注入,而不使用實(shí)際的對象,否則測試不可控饰潜。

      Mock優(yōu)先使用Spy方式:

      let masterService: MasterService;
      
      beforeEach(() => {
        const spy = jasmine.createSpyObj('ValueService', ['getValue']);
          spy.getValue.and.returnValue('stub value');
      
        TestBed.configureTestingModule({
          // Provide both the service-to-test and its (spy) dependency
          providers: [
            MasterService,
            { provide: ValueService, useValue: spy }
          ]
        });
      
        masterService = TestBed.get(MasterService);
      });
      
    • HttpClient初坠、Router、Location

      同測試含其它依賴的對象一樣彭雾,可以mock HttpClient碟刺、Router、Location:

      beforeEach(() => {
        const httpClientSpy = jasmine.createSpyObj('HttpClient', ['get']);
      
        TestBed.configureTestingModule({
          providers: [
            {provide: HttpClient, useValue: httpClientSpy}
          ]
        });
      });
      beforeEach(async(() => {
        const routerSpy = jasmine.createSpyObj('Router', ['navigateByUrl']);
        const locationSpy = jasmine.createSpyObj('Location', ['back']);
      
        TestBed.configureTestingModule({
          providers: [
            {provide: Router, useValue: routerSpy},
            {provide: Location, useValue: locationSpy}
          ]
        })
          .compileComponents();
      }));
      
    • Component測試

      • 僅測試組件類

    測試組件類就像測試服務(wù)那樣簡單:
    組件類

      export class WelcomeComponent  implements OnInit {
        welcome: string;
        constructor(private userService: UserService) { }
      
        ngOnInit(): void {
          this.welcome = this.userService.isLoggedIn ?
            'Welcome, ' + this.userService.user.name : 'Please log in.';
        }
      }
    

    Mock類

      class MockUserService {
        isLoggedIn = true;
        user = { name: 'Test User'};
      };
    

    測試

      ...
      beforeEach(() => {
        TestBed.configureTestingModule({
          // provide the component-under-test and dependent service
          providers: [
            WelcomeComponent,
            { provide: UserService, useClass: MockUserService }
          ]
        });
        // inject both the component and the dependent service.
        comp = TestBed.get(WelcomeComponent);
        userService = TestBed.get(UserService);
      });
      ...
      it('should ask user to log in if not logged in after ngOnInit', () => {
        userService.isLoggedIn = false;
        comp.ngOnInit();
        expect(comp.welcome).not.toContain(userService.user.name);
        expect(comp.welcome).toContain('log in');
      });
    
    • 組件DOM測試

    只涉及類的測試可以判斷組件類的行為是否正常薯酝,但不能確定組件是否能正常渲染和交互半沽。
    進(jìn)行組件DOM測試,需要使用TestBed.createComponent()等方法吴菠,第一個測試即為組件DOM測試者填。

      TestBed.configureTestingModule({
        declarations: [ BannerComponent ]
      });
      const fixture = TestBed.createComponent(BannerComponent);
      const component = fixture.componentInstance;
      expect(component).toBeDefined();
      ```
    
    **dispatchEvent**
      為模擬用戶輸入,比如為input元素輸入值做葵,要找到input元素并設(shè)置它的 value 屬性占哟。Angular不知道你設(shè)置了input元素的value屬性,需要調(diào)用 dispatchEvent() 觸發(fā)輸入框的 input 事件酿矢,再調(diào)用 detectChanges():
    
    ```typescript
      it('should convert hero name to Title Case', () => {
        // get the name's input and display elements from the DOM
        const hostElement = fixture.nativeElement;
        const nameInput: HTMLInputElement = hostElement.querySelector('input');
        const nameDisplay: HTMLElement = hostElement.querySelector('span');
      
        nameInput.value = 'quick BROWN  fOx';
      
        // dispatch a DOM event so that Angular learns of input value change.
        nameInput.dispatchEvent(newEvent('input'));
      
        fixture.detectChanges();
      
        expect(nameDisplay.textContent).toBe('Quick Brown  Fox');
      });
      ```
    
    - 嵌套組件
    
    組件中常常使用其他組件:
    
    ```html
    <app-banner></app-banner>
    <app-welcome></app-welcome>
    <nav>
      <a routerLink="/dashboard">Dashboard</a>
      <a routerLink="/heroes">Heroes</a>
      <a routerLink="/about">About</a>
    </nav>
    <router-outlet></router-outlet>
    

    對于無害的內(nèi)嵌組件可以直接將其添加到declarations中榨乎,這是最簡單的方式:

    describe('AppComponent & TestModule', () => {
      beforeEach(async(() => {
        TestBed.configureTestingModule({
          declarations: [
            AppComponent,
            BannerComponent,
            WelcomeComponent
          ]
        })
        .compileComponents().then(() => {
          fixture = TestBed.createComponent(AppComponent);
          comp    = fixture.componentInstance;
        });
      }));
      ...
    });
    

    也可為無關(guān)緊要的組件創(chuàng)建一些測試樁:

    @Component({selector: 'app-banner', template: ''})
    class BannerStubComponent {}
    
    @Component({selector: 'router-outlet', template: ''})
    class RouterOutletStubComponent { }
    
    @Component({selector: 'app-welcome', template: ''})
    class WelcomeStubComponent {}
    

    然后在TestBed的配置中聲明它們:

    TestBed.configureTestingModule({
      declarations: [
        AppComponent,
        BannerStubComponent,
        RouterOutletStubComponent,
        WelcomeStubComponent
      ]
    })
    

    另一種辦法是使用NO_ERRORS_SCHEMA,要求 Angular編譯器忽略那些不認(rèn)識的元素和屬性:

    TestBed.configureTestingModule({
      declarations: [
        AppComponent,
        RouterLinkDirectiveStub
      ],
      schemas: [ NO_ERRORS_SCHEMA ]
    })
    

    NO_ERRORS_SCHEMA方法比較簡單瘫筐,但不要過度使用蜜暑。NO_ERRORS_SCHEMA 會阻止編譯器因疏忽或拼寫錯誤而缺失的組件和屬性,如人工找出這些 bug會很費(fèi)時策肝。
    RouterLinkDirectiveStub

    import { Directive, Input, HostListener } from '@angular/core';
    
    @Directive({
      selector: '[routerLink]'
    })
    export class RouterLinkDirectiveStub {
      @Input('routerLink') linkParams: any;
      navigatedTo: any = null;
    
      @HostListener('click')
      onClick() {
        this.navigatedTo = this.linkParams;
      }
    }
    
    • 屬性指令測試

      import { Directive, ElementRef, Input, OnChanges } from '@angular/core';
      
      @Directive({ selector: '[highlight]' })
      /** Set backgroundColor for the attached element to highlight color and set the element's customProperty to true */
      export class HighlightDirective implements OnChanges {
      
        defaultColor =  'rgb(211, 211, 211)'; // lightgray
      
        @Input('highlight') bgColor: string;
      
        constructor(private el: ElementRef) {
          el.nativeElement.style.customProperty = true;
        }
      
        ngOnChanges() {
          this.el.nativeElement.style.backgroundColor = this.bgColor || this.defaultColor;
        }
      }
      

      屬性型指令肯定要操縱 DOM肛捍,如只針對類測試不能證明指令的有效性。若通過組件來測試之众,單一的用例一般無法探索指令的全部能力拙毫。因此,更好的方法是創(chuàng)建一個能展示該指令所有用法的人造測試組件:

      @Component({
        template: `
        <h2 highlight="yellow">Something Yellow</h2>
        <h2 highlight>The Default (Gray)</h2>
        <h2>No Highlight</h2>
        <input #box [highlight]="box.value" value="cyan"/>`
      })
      class TestComponent { }
      

      測試程序:

      beforeEach(() => {
        fixture = TestBed.configureTestingModule({
          declarations: [ HighlightDirective, TestComponent ]
        })
        .createComponent(TestComponent);
      
        fixture.detectChanges(); // initial binding
      
        // all elements with an attached HighlightDirective
        des = fixture.debugElement.queryAll(By.directive(HighlightDirective));
      
        // the h2 without the HighlightDirective
        bareH2 = fixture.debugElement.query(By.css('h2:not([highlight])'));
      });
      
      // color tests
      it('should have three highlighted elements', () => {
        expect(des.length).toBe(3);
      });
      
      it('should color 1st <h2> background "yellow"', () => {
        const bgColor = des[0].nativeElement.style.backgroundColor;
        expect(bgColor).toBe('yellow');
      });
      
      it('should color 2nd <h2> background w/ default color', () => {
        const dir = des[1].injector.get(HighlightDirective) as HighlightDirective;
        const bgColor = des[1].nativeElement.style.backgroundColor;
        expect(bgColor).toBe(dir.defaultColor);
      });
      
      it('should bind <input> background to value color', () => {
        // easier to work with nativeElement
        const input = des[2].nativeElement as HTMLInputElement;
        expect(input.style.backgroundColor).toBe('cyan', 'initial backgroundColor');
      
        // dispatch a DOM event so that Angular responds to the input value change.
        input.value = 'green';
        input.dispatchEvent(newEvent('input'));
        fixture.detectChanges();
      
        expect(input.style.backgroundColor).toBe('green', 'changed backgroundColor');
      });
      
      it('bare <h2> should not have a customProperty', () => {
        expect(bareH2.properties['customProperty']).toBeUndefined();
      });
      
    • Pipe測試

      describe('TitleCasePipe', () => {
        // This pipe is a pure, stateless function so no need for BeforeEach
        let pipe = new TitleCasePipe();
      
        it('transforms "abc" to "Abc"', () => {
          expect(pipe.transform('abc')).toBe('Abc');
        });
      
        it('transforms "abc def" to "Abc Def"', () => {
          expect(pipe.transform('abc def')).toBe('Abc Def');
        });
      
        ...
      });
      
    • Testing Module

      RouterTestingModule
      在前面的測試中我們使用了測試樁RouterOutletStubComponent棺禾,與Router有關(guān)的測試還可以使用RouterTestingModule:

      beforeEach(async(() => {
        TestBed.configureTestingModule({
          imports: [
            RouterTestingModule
          ],
          declarations: [
            AppComponent
          ],
        }).compileComponents();
      }));
      

      RouterTestingModule還可以模擬路由:

      beforeEach(() => {
        TestBed.configureTestModule({
          imports: [
            RouterTestingModule.withRoutes(
              [{path: '', component: BlankCmp}, {path: 'simple', component: SimpleCmp}]
            )
          ]
        });
      });
      

      HttpClientTestingModule

      describe('HttpClient testing', () => {
        let httpClient: HttpClient;
        let httpTestingController: HttpTestingController;
      
        beforeEach(() => {
          TestBed.configureTestingModule({
            imports: [ HttpClientTestingModule ]
          });
      
          // Inject the http service and test controller for each test
          httpClient = TestBed.get(HttpClient);
          httpTestingController = TestBed.get(HttpTestingController);
        });
      
        afterEach(() => {
          // After every test, assert that there are no more pending requests.
          httpTestingController.verify();
        });
      
        it('can test HttpClient.get', () => {
          const testData: Data = {name: 'Test Data'};
      
          // Make an HTTP GET request
          httpClient.get<Data>(testUrl)
            .subscribe(data =>
              // When observable resolves, result should match test data
              expect(data).toEqual(testData)
            );
      
          // The following `expectOne()` will match the request's URL.
          // If no requests or multiple requests matched that URL
          // `expectOne()` would throw.
          const req = httpTestingController.expectOne('/data');
      
          // Assert that the request is a GET.
          expect(req.request.method).toEqual('GET');
      
          // Respond with mock data, causing Observable to resolve.
          // Subscribe callback asserts that correct data was returned.
          req.flush(testData);
      
          // Finally, assert that there are no outstanding requests.
          httpTestingController.verify();
        });
      
          ...
      });
      
  • 在Mock的時候恬偷,優(yōu)先推薦使用Spy

    使用方式簡介:

    spyOn(obj, 'functionName').and.returnValue(returnValue);
    
    spyOn(storeStub, 'select').and.callFake(func => {
            func();
            return returnValue;
    });
    

    在需要模擬返回值的地方使用spy監(jiān)視對象以及其調(diào)用的函數(shù),按上述兩種方式可以自定義返回值帘睦。

    更詳細(xì)的用法參照

    https://jasmine.github.io/2.5/introduction#section-Spies

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市坦康,隨后出現(xiàn)的幾起案子竣付,更是在濱河造成了極大的恐慌,老刑警劉巖滞欠,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件古胆,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)逸绎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進(jìn)店門惹恃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人棺牧,你說我怎么就攤上這事巫糙。” “怎么了颊乘?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵参淹,是天一觀的道長。 經(jīng)常有香客問我乏悄,道長浙值,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任檩小,我火速辦了婚禮开呐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘规求。我一直安慰自己筐付,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布颓哮。 她就那樣靜靜地躺著家妆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪冕茅。 梳的紋絲不亂的頭發(fā)上伤极,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天,我揣著相機(jī)與錄音姨伤,去河邊找鬼哨坪。 笑死,一個胖子當(dāng)著我的面吹牛乍楚,可吹牛的內(nèi)容都是我干的当编。 我是一名探鬼主播,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼徒溪,長吁一口氣:“原來是場噩夢啊……” “哼忿偷!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起臊泌,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤鲤桥,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后渠概,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體茶凳,經(jīng)...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡嫂拴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了贮喧。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片筒狠。...
    茶點(diǎn)故事閱讀 39,739評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖箱沦,靈堂內(nèi)的尸體忽然破棺而出辩恼,到底是詐尸還是另有隱情,我是刑警寧澤饱普,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布运挫,位于F島的核電站,受9級特大地震影響套耕,放射性物質(zhì)發(fā)生泄漏谁帕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一冯袍、第九天 我趴在偏房一處隱蔽的房頂上張望匈挖。 院中可真熱鬧,春花似錦康愤、人聲如沸儡循。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽择膝。三九已至,卻和暖如春检激,著一層夾襖步出監(jiān)牢的瞬間肴捉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工叔收, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留齿穗,地道東北人。 一個月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓饺律,卻偏偏與公主長得像窃页,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子复濒,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,647評論 2 354