I want to mock a global variable and test if it has been called in my function. In my file, I define a global variable "remote" which instanciates a Subject (RxJS library). I just want to test if the next function of Subject has been called with the right parameter "name". However, I can't access the global variable remote in my test file. I tried to mock it in my setup file, but doesn't work.
How can I do that ?
const remote = new Subject();
const analytics = {
click: (name) => {
if (name) {
remote.next(name);
}
}
}
module.exports = analytics;
Thanks !
This is a good question. When you use webpack, your individual file is wrapped into different function call. Check out this doc.
The best part of this question is that it shows the necessity of IOC/DI if you want your code to be testable. Instead of defining remote in your local module, you can export an Analytics class and then inject remote to its constructor.
// Analytics.js
export default class Analytics{
constructor(remote) {
this.remote = remote
}
click(name) {
if (name) {
this.remote.next(name)
}
}
}
main.js
import Analytics from './Analytics'
const remote = new Subject()
const analytics = new Analytics(remote)
analysis.click('foo')
It would be tedious to inject dependencies to all components/services. Angular has an decent doc on why/how to simplify it. Hope this is helpful!
// Update
You can use window to define global constant and access it in tests by using window.remote
const remote = new Subject()
window.remote = remote
When you want to mock remote, remember to modify its properties rather than the reference to it.
// test.js
beforeEach(() => {
// wrong !!!
window.remote = {
next(name) { assert(name) }
}
// right
window.remote.next = name => assert(name)
})
Related
Currently I am writing unit tests about vscode extension. But some functions are using extensionContext and I can't get extensionContext in unit tests. Any way to get it?
Just came across this question, because I had exactly the same problem.
It looks like you can do the following in a test:
const ext = vscode.extensions.getExtension("publisher.extensionName");
And you can return anything from your activate function, so you could decide to return the extension context (or anything else that you need) there:
export async function activate(
context: vscode.ExtensionContext
): Promise<vscode.ExtensionContext> {
// Your activation code...
return context;
}
And then you can access the context in the test:
const ext = vscode.extensions.getExtension("publisher.extensionName");
const myExtensionContext = await ext.activate();
You find publisher.extensionName information in package.json of the extension:
{
"publisher": "myself",
"name": "myextension",
"displayName": "My Extension",
"description": "",
"version": "1.0.0",
...
Another idea is to add a command that returns the context:
export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(vscode.commands.registerCommand('getContext', () => context));
}
and then get the context by:
const context = await vscode.commands.executeCommand("getContext") as vscode.ExtensionContext;
But be careful not to expose the context in production
This is an answer to an old question, but I've had the same problem and I'm answering it now, so others can see it.
I found this solution in https://github.dev/microsoft/vscode extensions/vscode-api-test/src/extension.ts and extensions/vscode-api-test/src/singlefolder-test/state.test.ts files.
First, the context entered in the activate function must be registered in the global as shown below.
import * as vscode from 'vscode';
export function activate(_context: vscode.ExtensionContext) {
// Set context as a global as some tests depend on it
(global as any).testExtensionContext = _context;
}
After that, you can use it as follows in the file to be tested.
import * as assert from 'assert';
import 'mocha';
import { ExtensionContext, extensions } from 'vscode';
suite('vscode API - globalState / workspaceState', () => {
let extensionContext: ExtensionContext;
suiteSetup(async () => {
// Trigger extension activation and grab the context as some tests depend on it
await extensions.getExtension('vscode.vscode-api-tests')?.activate();
extensionContext = (global as any).testExtensionContext;
});
test('state', async () => {
// Do some tests here using extensionContext
}
});
});
In the signature of the activate function, context is received as a parameter, but I don't know why, but here it can be used without setting context as a parameter. It may be that the corresponding value is entered automatically.
I have to unfortunately write Unit Tests for a legacy Sitecore MVC code base where two distinct Sitecore Contexts are called. I understand this comes under Integration Testing but i don't have the option of educating my project Leads on that front. So i have chosen to use FakeDb for emulating Sitecore Instance and NSubstitute for substituting injected Dependencies (can't use any Profilier API Frameworks like MS Fakes, TypeMock etc because of Budget constraints). I am providing the code below:
Method to be UnitTested
public bool DubiousMethod()
{
// This HttpContext call is pain area 1. This gets resolved when i call it using ItemContextSwitcher in Unit Tests.
string currentUrl = HttpContext.Current.Request.RawUrl;
// This Sitecore Context call to Site Name is pain area 2. This gets resolved when Unit Tests are run under SiteContextSwitcher.
string siteName = Sitecore.Context.Site.Name;
return true/False;
}
Unit Test Method
[Fact]
public void DubiousMethodUT()
{
// create a fake site context
var fakeSite = new Sitecore.FakeDb.Sites.FakeSiteContext(
new Sitecore.Collections.StringDictionary
{
{ "name", "website" }, { "database", "web" }, { "rootPath", "/sitecore/content/home" },
{ "contentStartItem", "home"}, {"hostName","https://www.myorignalsiteurl.com"}
});
using (new Sitecore.Sites.SiteContextSwitcher(fakeSite))
{
//DubiousClassObject.DubiousMethod(home) // When Debugging after uncommenting this line i get correct value in **Sitecore.Context.Site.Name**
using (Sitecore.FakeDb.Db db = new Sitecore.FakeDb.Db
{
new Sitecore.FakeDb.DbItem("home") { { "Title", "Welcome!" } ,
new Sitecore.FakeDb.DbItem("blogs") }
})
{
Sitecore.Data.Items.Item home = db.GetItem("/sitecore/content/home");
//bool abc = confBlogUT.IsBlogItem(home);
using (new ContextItemSwitcher(home))
{
string siteName = Sitecore.Context.Site.Name;
var urlOptions = new Sitecore.Links.UrlOptions();
urlOptions.AlwaysIncludeServerUrl = true;
var pageUrl = Sitecore.Links.LinkManager.GetItemUrl(Sitecore.Context.Item, urlOptions);
HttpContext.Current = new HttpContext(new HttpRequest("", pageUrl.Substring(3), ""), new HttpResponse(new StringWriter()));
Assert.False(DubiousClassObject.DubiousMethod(home); //When Debugging after commenting above DubiousMethodCall i get correct value for **HttpContext.Current.Request.RawUrl**
}
}
}
}
As you can observe that when i try to call the method from FakSiteContext then i am getting the correct value for Sitecore.Context.Site.Name however my code breaks when HttpContext.Current.Request.RawUrl is invoked in the method. Opposite happens when i invoke the method from ContextItemSwitcher(FakeItem) context. So far i have not been able to find a way to merge both the Contexts (which i believe is impossible in Sitecore). Can anyone suggest if i run my Unit Tests in an overarching context where i am able to contrl fakeSite Variables as well as FakeItem context variables as well and by extensions any other Sitecore Context calls?
Any help would be appreciated.
I'd recommend to take a look at Unit testing in Sitecore article as it seem to be what you need.
In short - you'll need to do a few adjustments in your code to make it testable:
1) Replace static HttpContext with abstract HttpContextBase (impl. HttpContextWrapper) so that everything can be arranged - DubiousMethod gets an overload that accepts DubiousMethod(HttpContextBase httpContext).
2) As for Sitecore Context data - it has Sitecore.Caching.ItemsContext-bound semantics (as mentioned in the article), so you could cleanup the collection before/after each test to get a sort of isolation between tests.
Alternatively you could bake a similar wrapper for Sitecore.Context as ASP.NET team had done for HttpContext -> HttpContextBase & impl HttpContextWrapper.
Apparently mock.mockRestore() does not restore the original implementation of a mock created using jest.mock()
// a.js
export default class A {}
// b.js
import A from './a';
export default class B extends A {}
// test.js
import A from './a';
import B from './b';
jest.mock('./a');
jest.mock('./b');
const b = new B();
test('instanceOf', () => {
A.mockRestore();
B.mockRestore();
expect(b).toBeInstanceOf(A); // fails
});
mockFn.mockRestore only works for a mock function created with jest.spyOn:
const obj = {
func: () => 'original'
}
test('func', () => {
const mock = jest.spyOn(obj, 'func');
mock.mockReturnValue('mocked');
expect(obj.func()).toBe('mocked'); // Success!
mock.mockRestore();
expect(obj.func()).toBe('original'); // Success!
})
jest.spyOn wraps the original function and provides mockRestore as a way to restore the original function.
jest.mock calls work a little differently.
Jest takes over the require system and jest.mock tells Jest that it should return the module mock instead of the actual module whenever it is required.
This means that the module mock doesn't wrap the original module, it completely replaces the original module in the require system. So mockRestore may be defined on mock functions within the module mock, but calling it doesn't restore the original implementation.
jest.mock is typically used when you want to mock an entire module for the whole test.
It is particularly useful when using ES6-style import statements since babel-jest hoists jest.mock calls and they run before anything else in the test file (including any import statements):
import A from './a'; // <= A is already mocked...
jest.mock('./a'); // <= ...because this runs first
test('A', () => {
// ...
}
There isn't an easy way to restore the original module during a test that uses jest.mock since its primary use is to mock a module for an entire test.
If you are trying to use both a mock and the original implementation during the same test there are a few options:
Mock one particular function using jest.spyOn and restore it using mockRestore
Use jest.doMock to avoid the hoisting behavior of jest.mock...just note you also need to use require within the scope that uses jest.doMock instead of using a top-level import
Use jest.requireActual at any time to require the original module
Assuming you cant use spyOn,
you can do something like this:
// test.js
jest.mock('./a');
import A from './a';
A.mockImplementation(params => 'mockVal');
const actualA = jest.requireActual('./a');
test('instanceOf', () => {
A.mockRestore(); // acts like mockReset()
A.mockImplementation((params) => {
return actualA(params);
});
});
I am adding Coldbox to our legacy application and I ran into a problem where we can't access certain variables from within the views when using Coldbox. In the existing legacy code inside of Application.cfc in the onRequestStart method we set several variables like so:
VARIABLES.screenID = 0;
VARIABLES.DSN = 'datasourcemain';
VARIABLES.DSNRO = 'datasourcereadonly';
VARIABLES.DSNADMIN = 'datasourceadmin';
VARIABLES.pagetitle = "Default Page Title for web application";
This is just a small snippet of the variables set. The problem is that in the legacy code these were used all over the place like in the header and footer. When browsing to a legacy page these are still accessible but when sending the request through coldbox the variables become inaccessible. My question is, is there a recommended way that I can have Coldbox know about these variables and pass them along to the views so I don't have to modify hundreds of files?
It depends, there are a few places to define such variables. From the limited information given, I would suggest you add the datasource information to the Coldbox.cfc > datasources struct (#1) and add the default pageTitle to the global request handler (#2). As for the screenID, who knows -- good luck!
config/Coldbox.cfc has both a settings and datasources struct that can be injected via wirebox into the handlers/controllers.
// Dependency Injection using WireBox
property name='settings' inject='coldbox:settings';
Use a global request handler and add all the global vars into the prc (private request context) which is visible to the controller and views.
//config/Coldbox.cfc
...
coldbox = {
requestStartHandler: 'Main.onRequestStart'
};
...
// handlers/Main.cfc
component extends='coldbox.system.EventHandler' {
function onRequestStart( event, rc, prc) {
prc.screenID = 0;
prc.DSN = 'datasourcemain';
prc.DSNRO = 'datasourcereadonly';
prc.DSNADMIN = 'datasourceadmin';
prc.pagetitle = "Default Page Title for web application";
}
}
Use a request interceptor and add data to the prc.
//config/Coldbox.cfc
...
interceptors = [
{ class="interceptors.Globals" }
];
...
//interceptor/Globals.cfc
component {
property name='legacyGlobals' inject='LegacyGlobals';
function preProcess(event, interceptData) {
event.setPrivateValue('someLegacyGlobalVar', legacyGlobals.getSomeLegacyGlobalVar() );
}
}
Is it possible to store common javascript functions centrally for use in pre-request script?
In a "Postman Run" with several separate api calls I need to generate a new datetimeoffset for each call. Currently, I copy the getDateTimeOffSet javascript function to the "pre-request script" tab of each call.
Ideally I would have a central place to store helper function and be able to call these functions from the individual pre-request scripts.
Unfortunately, the only way to re-use function in postman - is to store them as a global/environment variable. You may try to create a separate request and initiate some kind of utils module and then add it to the scope of your variables:
pm.environment.set('utils', () => {
const generateRandomText = () => {
//generate random text
}
const generateUsername = () => {
//generate username
}
return {
testPackage: {
generateRandomText,
generateUsername
}
};
});
In Pre-request scripts and Tests of other request you can simply call:
const utils = eval(environment.utils)();
const text = utils.testPackage.generateRandomText();
const username utils.testPackage.generateUsername();