Angular应用测试全解析
1. 单元测试基础
在Angular应用的单元测试中,有几个关键要点需要注意。首先,在测试HTTP请求时,
verify()
方法可以用来测试所有的HTTP请求,并且在每个测试用例运行后的清理阶段,我们可以断言没有未完成的请求。同时,每个测试用例的代码通常会被包裹在
async()
函数中,这能确保
expect()
调用在所有异步调用完成后执行。
对于使用路由的组件测试,Angular提供了
RouterTestingModule
,它可以拦截导航但不加载目标组件。测试时,我们可以使用应用中相同的路由配置,也可以为测试创建单独的配置。用户可以通过与应用交互或直接在浏览器地址栏输入URL来导航应用,
Router
对象负责应用代码中的导航,
Location
对象代表地址栏中的URL,二者协同工作。
为了测试路由是否能正确导航应用,我们可以在测试用例中调用
navigate()
和
navigateByUrl()
方法,并根据需要传递参数。例如,下面是一个路由配置的示例:
export const routes: Routes = [
{path: '', component: HomeComponent},
{path: 'product/:id', component: ProductDetailComponent}
];
当用户点击“Product Details”链接时,应用会导航到
ProductDetailComponent
。以下是相关的组件代码:
@Component({
selector: 'app-root',
template: `
<a [routerLink]="['/']">Home</a>
<a id="product" [routerLink]="['/product', productId]">
Product Detail</a>
<router-outlet></router-outlet>
`
})
export class AppComponent {
productId = 1234;
}
在测试中,我们可以通过以下步骤来验证点击链接后URL是否正确:
1. 使用
TestBed.get()
API注入
Router
和
Location
对象。
2. 使用
By.css()
API获取对应的DOM对象,模拟点击链接。
3. 使用
triggerEventHandler()
方法模拟点击事件。
4. 使用
fakeAsync()
函数包裹导航代码,
tick()
函数确保异步导航完成后再进行断言。
以下是具体的测试代码:
// imports omitted for brevity
describe('AppComponent', () => {
let fixture;
let router: Router;
let location: Location;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [RouterTestingModule.withRoutes(routes)],
declarations: [
AppComponent, ProductDetailComponent, HomeComponent
]
}).compileComponents();
}));
beforeEach(fakeAsync(() => {
router = TestBed.get(Router);
location = TestBed.get(Location);
fixture = TestBed.createComponent(AppComponent);
router.navigateByUrl('/');
tick();
fixture.detectChanges();
}));
it('can navigate and pass params to the product detail view',
fakeAsync(() => {
const productLink = fixture.debugElement.query(By.css('#product'));
productLink.triggerEventHandler('click', {button: 0});
tick();
fixture.detectChanges();
expect(location.path()).toEqual('/product/1234');
}));
});
下面是测试导航的步骤流程图:
graph LR
A[Configure test module] --> B[Inject router and location]
B --> C[Instantiate AppComponent]
C --> D[Click on the link]
D --> E[Locate the link]
E --> F[Product Detail]
F --> G[Update the UI of AppComponent]
G --> H[Update the UI of ProductDetailComponent]
H --> I[Assert that URL contains /product/1234]
2. 端到端测试概述
单元测试可以确保Angular应用的每个独立部分按预期工作,但如何确保多个组件、服务和其他部分协同工作正常,而无需手动测试每个工作流程呢?这就需要端到端(E2E)测试。
E2E测试通过模拟用户与应用的交互来测试整个应用的工作流程。例如,下单过程可能涉及多个组件和服务,我们可以创建一个E2E测试来确保这个工作流程按预期进行。与单元测试不同,E2E测试使用真实的依赖项。
Protractor是一个测试库,它允许我们模拟用户操作来测试应用工作流程,而无需手动执行。默认情况下,Protractor使用Jasmine语法进行测试。它基于Selenium WebDriver,可以自动驱动浏览器。
当使用Angular CLI生成新项目时,会包含Protractor及其配置文件,以及
e2e
目录和示例测试脚本。运行E2E测试时,我们可以使用
ng e2e
命令,它会根据
protractor.conf.js
文件的配置加载测试脚本。以下是
protractor.conf.js
文件的部分配置示例:
specs: [
'./e2e/**/*.e2e-spec.ts'
],
capabilities: {
'browserName': 'chrome'
},
directConnect: true
Protractor提供了一些API来与所有支持的框架一起工作:
-
browser
:提供控制浏览器的API,如
getCurrentUrl()
、
wait()
等。
-
by
:用于通过ID、CSS、按钮或链接文本等查找Angular应用中的元素。
-
element
:用于查找和处理网页上的单个元素。
-
element.all
:用于查找和处理元素集合,如遍历HTML列表或表格的元素。
我们可以使用以下两种方法编写E2E测试:
1. 在同一脚本中,使用元素的ID或CSS类定位DOM元素,并断言应用逻辑是否正确。但这种方法的ID或CSS类可能会随时间变化,需要更新多个脚本。
2. 实现Page Object设计模式,将期望和断言写在一个文件中,将与UI元素交互和调用应用API的代码写在另一个文件中。这种方法可以减少代码重复,提高代码的可维护性。
3. Angular CLI生成的E2E测试
当使用Angular CLI生成新项目时,会在
e2e
目录下创建三个文件:
-
app.po.ts
:
AppComponent
的页面对象。
-
app.e2e-spec.ts
:生成的
AppComponent
的E2E测试。
-
tsconfig.e2e.json
:TypeScript编译器选项。
app.po.ts
文件包含一个简单的
AppPage
类,有两个方法:
import {browser, by, element} from 'protractor';
export class AppPage {
navigateTo() {
return browser.get('/');
}
getParagraphText() {
return element(by.css('app-root h1')).getText();
}
}
app.e2e-spec.ts
文件的测试代码如下:
import {AppPage} from './app.po';
describe('e2e-testing-samples App', () => {
let page: AppPage;
beforeEach(() => {
page = new AppPage();
});
it('should display welcome message', () => {
page.navigateTo();
expect(page.getParagraphText()).toEqual('Welcome to app!');
});
});
这个测试代码很容易理解,通过导航到着陆页并获取段落内容进行断言。我们可以使用
ng e2e
命令运行这个E2E测试。
以下是相关文件及其功能的表格:
| 文件名称 | 功能描述 |
| ---- | ---- |
| app.po.ts | 包含页面对象类和相关方法,用于定位元素和执行操作 |
| app.e2e-spec.ts | 包含测试用例,使用页面对象的API进行测试断言 |
| tsconfig.e2e.json | TypeScript编译器选项,用于E2E测试 |
Angular应用测试全解析
4. 测试登录页面
接下来,我们将以一个包含登录页面和主页的简单应用为例,详细介绍如何编写E2E测试。该应用的路由配置如下:
[{path: '', redirectTo: 'login', pathMatch: 'full'},
{path: 'login', component: LoginComponent},
{path: 'home', component: HomeComponent}]
HomeComponent
的模板只有一行:
<h1>Home Component</h1>
LoginComponent
包含一个登录按钮和一个表单,用于输入ID和密码。如果用户输入的ID为
Joe
,密码为
password
,应用将导航到主页;否则,将停留在登录页面并显示错误消息“Invalid ID or password”。以下是
LoginComponent
的代码:
@Component({
selector: 'app-home',
template: `<h1 class="home">Login Component</h1>
<form #f="ngForm" (ngSubmit)="login(f.value)">
ID: <input name="id" ngModel/><br>
PWD: <input type="password" name="pwd" ngModel=""/><br>
<button type="submit">Login</button>
<span id="errMessage"
*ngIf="wrongCredentials">Invalid ID or password</span>
</form>
`
})
export class LoginComponent {
wrongCredentials = false;
constructor(private router: Router) {}
login(formValue) {
if ('Joe' === formValue.id && 'password' === formValue.pwd) {
this.router.navigate(['/home']);
this.wrongCredentials = false;
} else {
this.router.navigate(['/login']);
this.wrongCredentials = true;
}
}
}
测试代码位于
e2e
目录下,包含两个页面对象文件
login.po.ts
和
home.po.ts
,以及一个测试脚本
login.e2e-spec.ts
。
home.po.ts
文件包含一个方法,用于返回页面标题的文本:
import {by, element} from 'protractor';
export class HomePage {
getHeaderText() {
return element(by.css('h1')).getText();
}
}
login.po.ts
文件使用定位器获取表单字段和按钮的引用,并提供了模拟用户登录操作的方法:
import {browser, by, element, $} from 'protractor';
export class LoginPage {
id = $('input[name="id"]');
pwd = $('input[name="pwd"]');
submit = element(by.buttonText('Login'));
errMessage = element(by.id('errMessage'));
login(id: string, password: string): void {
this.id.sendKeys(id);
this.pwd.sendKeys(password);
this.submit.click();
}
navigateToLogin() {
return browser.get('/login');
}
getErrorMessage() {
return this.errMessage;
}
}
login.e2e-spec.ts
文件包含测试套件,用于测试登录流程的成功和失败情况:
import {LoginPage} from './login.po';
import {HomePage} from './home.po';
import {browser} from 'protractor';
describe('Login page', () => {
let loginPage: LoginPage;
let homePage: HomePage;
beforeEach(() => {
loginPage = new LoginPage();
});
it('should navigate to login page and log in', () => {
loginPage.navigateToLogin();
loginPage.login('Joe', 'password');
const url = browser.getCurrentUrl();
expect(url).toContain('/home');
homePage = new HomePage();
expect(homePage.getHeaderText()).toEqual('Home Component');
});
it('should stay on login page if wrong credentials entered',
() => {
loginPage.navigateToLogin();
loginPage.login('Joe', 'wrongpassword');
const url = browser.getCurrentUrl();
expect(url).toContain('/login');
expect(loginPage.getErrorMessage().isPresent()).toBe(true);
});
});
下面是登录测试的步骤流程图:
graph LR
A[Instantiate LoginPage] --> B[Navigate to login page]
B --> C{Enter credentials}
C -- Correct --> D[Navigate to home page]
D --> E[Assert URL contains /home]
E --> F[Assert page header is correct]
C -- Incorrect --> G[Stay on login page]
G --> H[Assert URL contains /login]
H --> I[Assert error message is shown]
5. 处理异步操作
在实际应用中,有些操作可能需要一些时间才能完成,例如登录时与认证服务器通信。在这种情况下,我们需要等待操作完成后再进行断言。Protractor提供了
browser.wait()
方法来处理这种情况。
例如,在点击搜索按钮后,应用会发起一个HTTP请求来获取产品信息,这个请求可能需要一些时间才能完成。我们可以使用一个辅助函数来等待URL变化后再进行断言。
在前面的登录测试中,如果登录过程需要与认证服务器通信,我们可以使用
browser.wait()
方法来确保在URL更新后再进行断言:
it('should navigate to login page and log in', () => {
loginPage.navigateToLogin();
loginPage.login('Joe', 'password');
browser.wait(() => {
return browser.getCurrentUrl().then(url => {
return url.includes('/home');
});
}, 5000, 'URL did not change to /home within 5 seconds');
const url = browser.getCurrentUrl();
expect(url).toContain('/home');
homePage = new HomePage();
expect(homePage.getHeaderText()).toEqual('Home Component');
});
6. 总结
通过以上内容,我们详细介绍了Angular应用的单元测试和端到端测试。单元测试可以确保每个独立部分按预期工作,而端到端测试可以模拟用户操作,确保整个应用的工作流程正常。
在单元测试中,我们学习了如何测试HTTP请求、使用路由的组件,以及如何处理异步操作。在端到端测试中,我们了解了Protractor的使用方法,包括其API、配置文件和编写测试的两种方法。
为了提高测试的可维护性和可扩展性,我们推荐使用Page Object设计模式。同时,在处理异步操作时,要注意使用
browser.wait()
方法来确保断言在操作完成后进行。
以下是单元测试和端到端测试的对比表格:
| 测试类型 | 测试范围 | 依赖项 | 测试方法 | 适用场景 |
| ---- | ---- | ---- | ---- | ---- |
| 单元测试 | 单个组件、服务等 | 模拟依赖项 | 编写测试用例,使用
async()
、
fakeAsync()
等函数处理异步操作 | 验证单个模块的功能 |
| 端到端测试 | 整个应用工作流程 | 真实依赖项 | 使用Protractor模拟用户操作,使用Page Object设计模式 | 验证多个模块协同工作的功能 |
希望这些内容能帮助你更好地进行Angular应用的测试工作。
超级会员免费看
623

被折叠的 条评论
为什么被折叠?



