我正在向我公司的应用程序引入javascript单元测试。我主要使用AngularJS,但他们选择的框架是Knockout。代码在设计上是相对模块化的(感谢Knockout),所以我假设在代码周围添加单元测试很容易(就像Angular一样)。
然而,还有一个复杂因素:系统使用require.js
作为IoC容器。此外,我的测试运行器是Chutzpah,它在Visual Studio Test Explorer中是无头的。我试图使用SquireJS模拟出依赖关系,但是在尝试实例化ViewModel时我遇到了麻烦:它想要在Component中设置并继承的某些依赖项。我尝试加载具有进一步依赖性的组件,依此类推。下面我有一个简明扼要的设置版本,概述了这一点。实例化此视图模型的最佳方法是什么,以便我可以使用Squire模拟依赖项并运行我的测试?
组件
define(function (require) {
var Boiler = require('Boiler'),
BaseViewModel = require('../../../../core/baseClasses/BaseViewModel'),
viewTemplate = require('text!./view.html'),
viewModel = require('./viewModel'),
nls = require('i18n!./nls/resources');
var Component = function (initializingParameters) {
//...blah blah blah setup things
};
return Component;
});
视图模型
define(function (require) {
var Constants = require('../../../../core/constants');
var momDate = moment().format('MMM DD, YYYY');
var Utils = require("../../../../model/utils");
var utils = new Utils();
var otherComponentUtils = require("../../../otherModule/otherComponent/OtherComponentUtils");
var otherComponentUtils = new OtherComponentUtils();
var ViewModel = function (globalContext, moduleContext, dataContext, domElement) {
var Self = this;
Self.super(moduleContext, dataContext, resPickerContext);
var constants = new Constants();
var utils = require("../../../../model/utils");
var Utils = new utils();
Self.Items = ko.observableArray().extend({ throttle: 500 });
//a bunch more Knockout object setup
Self.GetAuthorizationTypesArray = function (itemId) {
dataContext.dataRequestHandler(dataContext.serviceConstants.GetTransactionTypes, { ItemId: itemId },
function (data) {
if (data != null && data.length !== 0) {
Self.TransactionTypeArray(data);
var transactionTypeArray = Self.TransactionTypeArray();
if (transactionTypeArray.length) {
var paymentInfo = Self.PaymentInfo();
if (paymentInfo !== null && paymentInfo !== undefined && paymentInfo.IsSpecial) {
var childThing = Self.ChildThing();
dataContext.dataRequestHandler(dataContext.serviceConstants.GetChild, { ChildId: childThing.ChildId, SpecialId: childThing.SpecialID }, function (data) {
var child = data[0];
var specialTypeId = child.ListId;
if (specialTypeId === 13)
Self.BigThing.Type(1);
else
Self.BigThing.Type(2);
}, Self.ShowError);
}
}
}
},
Self.ShowError);
}
return ViewModel;
});
chutzpah.json
{
"Framework": "jasmine",
"TestHarnessReferenceMode": "AMD",
"TestHarnessLocationMode": "SettingsFileAdjacent",
"RootReferencePathMode": "SettingsFileDirectory",
"References": [
{ "Path": "../../myWebApp/Scripts/libs/assets/plugins/jquery-1.10.2.min.js" },
{ "Path": "../../myWebApp/scripts/libs/moment/moment.min.js" },
{ "Path": "../../myWebApp/Scripts/libs/require/require.js" },
{ "Path": "unittest.main.js" }
],
"Tests": [
{
"Path": "app",
"Includes": [ "*.tests.js" ]
}
]
}
unittest.main.js
"use strict";
require.config({
paths: {
'jasmine': ['jasmine/jasmine'],
'jasmine-html': ['jasmine/jasmine-html'],
'jasmine-boot': ['jasmine/boot'],
squire: 'Squire'
},
shim: {
'jasmine-html': {
deps : ['jasmine']
},
'jasmine-boot': {
deps : ['jasmine', 'jasmine-html']
},
'squire': {
exports: 'squire'
}
},
config: {
text: {
useXhr: function (url, protocol, hostname, port) { return true },
//Valid values are 'node', 'xhr', or 'rhino'
env: 'xhr'
}
},
waitSeconds: 0
});
viewModel.tests.js
define(function (require) {
var testTargetPath = '../../myWebApp/Scripts/app/modules/thisModule/myViewModel';
describe('My View Model', function () {
var mockDataContext;
var mockResponseData;
var injector;
var viewModel;
beforeEach(function () {
var Squire = require('Squire');
injector = new Squire();
});
beforeEach(function () {
mockResponseData = {};
mockDataContext = {
dataRequestHandler: function (url, data, onSuccess) {
onSuccess(mockResponseData[url]);
},
serviceConstants: {
GetTransactionTypes: 'getTransactionTypes',
GetChild: 'getNewPolicy'
}
};
injector.mock("dataContext", mockDataContext);
});
beforeEach(function (done) {
injector.require([testTargetPath], function (ViewModel) {
viewModel = new ViewModel();
done();
});
});
it('gets authorization type array', function () {
spyOn(mockDataContext, 'dataRequestHandler').and.callThrough();
mockResponseData = {
'getTransactionTypes': [
{ name: 'auth type 1', TransactionTypeId: 90210 },
{ name: 'auth type 2', TransactionTypeId: 42 },
]
};
viewModel.GetAuthorizationTypesArray(9266);
expect(mockDataContext.dataRequestHandler).toHaveBeenCalledWith('getTransactionTypes', { ItemId: 9266 });
expect(viewModel.TransactionTypeArray()).toBe(mockResponseData);
});
});
});
具体而言,在ViewModel
中,当测试运行时,它会抱怨super
未定义。
答案 0 :(得分:0)
好吧,事实证明我必须处理许多问题:
super
失败是因为ES6
兼容性问题(因为PhantomJS仅支持ES5)。然而,
事实证明该项目没有使用ES6,但是
inheritance.js
,它手动构建超级依赖项。
该设置是在我们的Knockout的component
部分内完成的
解决方案,所以我在单元测试中复制了它。我没有对我的组件或视图模型进行任何更改,我的Chutzpah配置保持不变。但是,unittest.main.js
已更新,我的测试也是如此:
<强> unittest.main.js 强>
"use strict";
require.config({
paths: {
'jasmine': ['jasmine/jasmine'],
'jasmine-html': ['jasmine/jasmine-html'],
'jasmine-boot': ['jasmine/boot'],
squire: 'Squire',
"baseViewModel": '../../myWebApp/Scripts/app/core/baseClasses/BaseViewModel'
},
shim: {
'jasmine-html': {
deps : ['jasmine']
},
'jasmine-boot': {
deps : ['jasmine', 'jasmine-html']
},
'squire': {
exports: 'squire'
}
},
config: {
text: {
useXhr: function (url, protocol, hostname, port) { return true },
//Valid values are 'node', 'xhr', or 'rhino'
env: 'xhr'
}
},
waitSeconds: 0
});
<强> viewModel.tests.js 强>
define(function (require) {
var testTargetPath = '../../myWebApp/Scripts/app/modules/thisModule/myViewModel';
describe('View Model', function () {
var mockGlobalContext;
var mockModuleContext;
var mockDataContext;
var mockResponseData;
var injector;
var viewModel;
var mockUtils;
beforeEach(function () {
var Squire = require('Squire');
injector = new Squire();
});
beforeEach(function () {
mockResponseData = {};
mockGlobalContext = {};
mockUtils = {};
mockModuleContext = {};
mockDataContext = {
dataRequestHandler: function (url, data, onSuccess) {
onSuccess(mockResponseData[url]);
},
serviceConstants: {
GetTransactionTypes: 'getTransactionTypes',
GetChild: 'getNewPolicy'
}
};
injector.mock("../../../../model/utils", function () { return mockUtils; });
spyOn(mockDataContext, 'dataRequestHandler').and.callThrough();
});
beforeEach(function (done) {
injector.require([testTargetPath], function (ViewModel) {
var BaseViewModel = require('baseViewModel');
BaseObject.Create(ViewModel, BaseViewModel, [mockGlobalContext, mockModuleContext, mockDataContext]);
viewModel = new ViewModel(mockGlobalContext, mockModuleContext, mockDataContext);
done();
});
});
it('gets authorization type array', function () {
mockResponseData = {
'getGatewayTransactionTypes': [
{ name: 'auth type 1', TransactionTypeId: 90210 },
{ name: 'auth type 2', TransactionTypeId: 42 },
]
};
viewModel.GetAuthorizationTypesArray(9266);
expect(mockDataContext.dataRequestHandler).toHaveBeenCalledWith('getGatewayTransactionTypes', { ItemId: 9266 }, jasmine.any(Function), viewModel.ShowError);
expect(viewModel.TransactionTypeArray()).toEqual(mockResponseData['getGatewayTransactionTypes']);
});
});
});
通过这些更改,测试成功运行并通过。