Research
Security News
Malicious npm Package Targets Solana Developers and Hijacks Funds
A malicious npm package targets Solana developers, rerouting funds in 2% of transactions to a hardcoded address.
Moq for Typescript. Inspired by c# Moq library.
npm install moq.ts --save-dev
moq.ts as the original Moq library is intended to be simple to use, strongly typed (no magic strings!, and therefore full compiler-verified and refactoring-friendly) and minimalistic (while still fully functional!). Every each mock is an instance of Proxy object.
You can find a pretty full set of usages in the integration tests. Check out tests.integration folder.
import { Mock, It, Times } from "moq.ts";
interface ITestObject {
method(arg1: number, arg2: string): Date;
}
const values = ["a", "b", "c"];
const mock = new Mock<ITestObject>()
.setup(instance => instance.method(1, values[0]))
.returns(new Date(2016))
.setup(instance => instance.method(It.Is(value => value === 2), values[1]))
.callback(({args: [arg1, arg2]}) => new Date(2017 + arg1))
.setup(instance => instance.method(3, It.Is(value => value === values[2])))
.throws(new Error("Invoking method with 3 and c"));
const object = mock.object();
const actual = object.method(1, "a");
mock.verify(instance => instance.method(2, "a"), Times.Never());
import { Mock, It, Times, GetPropertyExpression } from "moq.ts";
interface ITestObject {
property1: number;
property2: number;
property3: number;
property4: number;
method(): void;
}
const property4Name = "property4";
const mock = new Mock<ITestObject>()
.setup(instance => instance.property1)
.returns(1)
.setup(instance => It.Is((expression: GetPropertyExpression) => expression.name === "property2"))
.returns(100)
.setup(instance => instance.property3)
.callback(() => 10 + 10)
.setup(instance => instance[property4Name])
.throws(new Error("property4 access"))
//since a method is a property that holds a pointer to a function
.setup(instance => instance.method)
.returns(() => {
console.log("The method was called")
});
const object = mock.object();
object.method();
mock.verify(instance => instance.property1, Times.Never());
The documentation on returned value from "set hook" on Proxy object
import { Mock, It, Times, SetPropertyExpression } from "moq.ts";
interface ITestObject {
property: number | any;
}
const value = {field: new Date()};
const mock = new Mock<ITestObject>()
.setup(instance => {
instance.property = 1
})
//true - allows the write operation
.returns(true as any)
.setup(instance => It.Is((expression: SetPropertyExpression) => expression.name === "property" && expression.value === 2))
//false - denies the write operation
.returns(false)
.setup(instance => {
instance.property = It.Is(value => value === 3)
})
// allows the write operation
.callback(() => true)
.setup(instance => {
instance.property = value
})
.throws(new Error("an object has been written into property"));
const object = mock.object();
object.property = 1;
mock.verify(instance => {
instance.property = 1
}, Times.Once());
mock-method.property.IntegrationTests.ts
import { Mock, It, Times } from "moq.ts";
interface ITestFunction {
(arg: number | any): string;
}
const value = {field: new Date()};
const mock = new Mock<ITestFunction>()
.setup(instance => instance(1))
.returns("called with 1")
.setup(instance => instance(2))
.callback(({args: [argument]}) => argument === 2 ? "called with 2" : `called with ${argument}`)
.setup(instance => instance(value))
.throws(new Error("Argument is object with date"))
.setup(instance => instance(It.Is(value => value === 4)))
.returns("called with 4");
const method = mock.object();
const actual = method(1);
mock.verify(instance => instance(1), Times.Once());
mock.verify(instance => instance(It.Is(value => value === 1)), Times.Exactly(1));
The feature does not support async/await expressions.
The library support auto mocking for deep members. Consider this case:
interface IChild {
get(): string;
}
interface IRoot {
child: IChild;
}
const value = "value";
const child = new Mock<IChild>()
.setup(instance => instance.get())
.returns(value)
.object();
const root = new Mock<IRoot>()
.setup(instance => instance.child)
.returns(child)
.object();
const actual = root.child.get();
expect(actual).toBe(value);
We have to create a child mock in order to set up "child" property of the root object. With auto mocking this case could be rewritten as following:
const root = new Mock<IRoot>()
// The root mock automatically creates a mock for the child property
.setup(instance => instance.child.get())
.returns(value)
.object();
const actual = root.child.get();
expect(actual).toBe(value);
At this moment It predicates are forbidden, except the last part of an expression. Consider this case:
const a = "a";
const b = "b";
const c = "c";
const object = new Mock<{ get(arg: string): { a: string; b: string; c: string } }>()
.setup(instance => instance.get("a").a)
.returns(a)
.setup(instance => instance.get("b").b)
.returns(a)
.setup(instance => instance.get(It.IsAny()).c)
.returns(b)
.object();
The library would create a brand new mock for every each setup and save it in an internal map, where the expression is a key.
import { MethodExpression } from "./expressions";
new Map<Expression, Mock>([
[new MethodExpression("get", ["a"]), new Mock()],
[new MethodExpression("get", ["b"]), new Mock()],
[new MethodExpression("get", [It.IsAny()]), new Mock()],
])
And here are 2 issues:
Those issues are not obvious and could lead to a hard detected behaviour. To prevent it and to give developers clean and robust API It predicates is forbidden in the auto-mocking feature.
However, in some cases it makes sense to disabled it:
import { ComplexExpressionGuard } from "moq.ts/internal";
const injectorConfig = new EqualMatchingInjectorConfig([], [{
provide: ComplexExpressionGuard,
useValue: {verify: () => undefined} as Readonly<ComplexExpressionGuard>,
deps: []
}]);
const svg = new Mock<Selection<SVGSVGElement, unknown, HTMLElement, any>>({injectorConfig})
.setup(instance => instance
.append("g")
.attr("fill", "none")
.attr("stroke-width", 1.5)
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.selectAll("g")
.data(data)
.join("g")
.append("path")
.attr("class", It.IsAny())
.attr("d", It.IsAny()))
.returns(path)
.object();
The library supports asynchronous function with a promise-based wrappers.
async/await expressions are not supported in the auto-mocking feature.
This rule will ask you to write expressions with async keyword.
const mock = new Mock<typeof fn>()
.setup(async instance => instance(1))
.returnsAsync(2)
.setup(async instance => instance(2))
.throwsAsync(exception)
.object();
async function fn(input: number) {
return input;
}
const exception = new Error();
const mock = new Mock<typeof fn>()
.setup(instance => instance(1))
.returnsAsync(2)
// equal to
// .returns(Promise.resolve(2))
.setup(instance => instance(2))
.throwsAsync(exception)
// equal to
// .returns(Promise.reject(exception))
.object();
const actual = await mock(1);
expect(actual).toBe(2);
try {
await mock(2);
} catch (e) {
expect(e).toBe(exception);
}
Due to the fact that some environments are not using the native Promise object the library provides adapters for resolved/rejected promise that could be overridden. See ResolvedPromiseFactory , RejectedPromiseFactory.
Despite the fact that Mock class is generic type where T parameter stands for mocked type it works only at design time. At the runtime phase the type is not available and Moq library relays on other ways to discover the type information. It is required to implement correct behaviour of mocked object.
Consider this case:
class Prototype {
method(): void {
throw new Error("Not Implemented");
}
}
const object = new Mock<Prototype>()
.object();
const actual = object.method(); // throws TypeError: object.method is not a function
expect(actual).toBe(undefined);
It happens because at runtime the mock does not know that method is a part of the mocked type. So at the moment there are 3 ways how type could be discovered at runtime.
It is possible to provide a target object instance when a new instance of mock is being created. It will fix typeof operator. The prototype of the target will be used for type discovering and fixing instanceof operator.
By default, a mock is instantiated as Function object, so at runtime the mock "knows" about Function inherited properties and methods.
class Prototype {
method(): void {
throw new Error("Not Implemented");
}
}
const object = new Mock<Prototype>({target: new Prototype()})
.object();
const actual = object.method();
expect(actual).toBe(undefined);
expect(typeof object).toBe("object");
expect(object instanceof Prototype).toBe(true);
Another way to deal with type discovering is to provide a prototype object.
class Prototype {
method(): void {
throw new Error("Not Implemented");
}
}
const object = new Mock<Prototype>()
.prototypeof(Prototype.prototype)
.object();
const actual = object.method();
expect(actual).toBe(undefined);
expect(typeof object).toBe("function");
expect(object instanceof Prototype).toBe(true);
In some cases Moq library can discover type information from provided setup information.
class Prototype {
method(): number {
throw new Error("Not Implemented");
}
}
const object = new Mock<Prototype>()
.setup(instance => instance.method()) // this would be used for type discovering
.returns(2)
.object();
const actual = object.method();
expect(actual).toBe(2);
expect(typeof object).toBe("function");
A mocked object is a Proxy, that configured to track any read and write operations on properties. If you write a value to an arbitrary property the mocked object will keep it, and you can read it later on. By default, the prototype of mocked object is Function.
Accessing to an unset property or a method will return undefined, or a pointer to a spy function if it exists on prototype; You can call this function, and it will be tracked.
The default behaviour has the lowest precedence. The latest setup has the highest precedence.
You can control mock behavior when accessing to a property without a corresponding setup.
const mock = new Mock<ITestObject>();
mock.setup(() => It.IsAny())
.throws(new Error("setup is missed"));
It is possible to define a predicate that defines if provided setup can handle an interaction.
import { PlayTimes } from "moq.ts";
class Prototype {
method(): number {
throw new Error("Not Implemented");
}
}
const object = new Mock<Prototype>()
.setup(instance => instance.method())
.returns(4)
.setup(instance => instance.method())
// it could be any predefined funtion of PlayTimes or a custom function
.play(PlayTimes.Once())
.returns(2)
.object();
expect(object.method()).toBe(2);
expect(object.method()).toBe(4);
The latest setup has the highest precedence. And it says that it could handle only one interaction. After that the setup will be ignored and next setup would be taken.
Internally the library is using an injector that implementation is based on Angular injector to create and configure every each Mock object that is created with its constructor.
new Mock() // <-- calls angular based injector internally to create all dependencies
The library provides an extension point to change the way how mocks are configured internally. It is available through IMockOptions.injectorConfig that could be applied globally or per mock instance at the instancing phase.
import { EqualMatchingInjectorConfig, Mock } from "moq.ts";
// The global configuration that would be overridden with instance options
Mock.options = {injectorConfig: new EqualMatchingInjectorConfig()};
// or per instance
new Mock({injectorConfig: new EqualMatchingInjectorConfig()})
Out of the box there are 2 available configurations that change the way how a mock compares expressions.
This is a default configuration that provides the standard mock behaviours.
By default, all values are matched with Equality comparisons and sameness that is limited in matching objects. On the other hand developers are using so called "deep equal comparison" approach, where objects are matched by its properties and values. This configuration changes the way how expressions are matched and introduce deep equal comparison logic as well as an extension point for custom matchers.
import { Mock } from "moq.ts";
const mock = new Mock<(args: number[]) => number>()
.setup(instance => instance([2, 1]))
.returns(2);
const object = mock.object();
const actual = object([2, 1]);
// since the default comparisons logic sees [2, 1] and [2, 1] as different objects the provided setup would not work
expect(actual).toBe(undefined);
and compare with
import { EqualMatchingInjectorConfig, Mock } from "moq.ts";
const mock = new Mock<(args: number[]) => number>({injectorConfig: new EqualMatchingInjectorConfig()})
.setup(instance => instance([2, 1]))
.returns(2);
const object = mock.object();
const actual = object([2, 1]);
expect(actual).toBe(2);
Internally the equal comparision logic implemented as a collection of object matchers that implement IObjectMatcher interface.
Matchers with the most specific logic should come first in the collection and if they are not able to match the objects then more general matchers would be invoked.
The library comes with the following matchers:
Here is an example of a custom matcher that matches Moment and Date objects.
import { EqualMatchingInjectorConfig, IObjectMatcher, Mock, OBJECT_MATCHERS } from "moq.ts";
import { isMoment, utc } from "moment";
class MomentDateMatcher implements IObjectMatcher {
matched<T extends object>(left: T, right: T): boolean | undefined {
if (left instanceof Date && isMoment(right)) {
return left.valueOf() === right.valueOf();
}
return undefined;
}
}
const moment = utc(1);
const injectorConfig = new EqualMatchingInjectorConfig([{
provide: OBJECT_MATCHERS,
useClass: MomentDateMatcher,
multi: true,
deps: []
}]);
const mock = new Mock<(args: any) => number>({injectorConfig})
.setup(instance => instance(moment))
.returns(2);
const object = mock.object();
const actual = object(new Date(1));
expect(actual).toBe(2);
The matching logic of EqualMatchingInjectorConfig supports It notation . So you can do a partial comparision.
import { EqualMatchingInjectorConfig, It, Mock } from "moq.ts";
const func = () => undefined;
const injectorConfig = new EqualMatchingInjectorConfig();
const mock = new Mock<(args: any) => number>({injectorConfig})
.setup(instance => instance({func: It.IsAny()})) // <-- func property will be matched with It delegate
.returns(2);
const object = mock.object();
const actual = object({func});
expect(actual).toBe(2);
The moq.ts library is comprised of small units that follow SOLID principles. Some of those units are included in the public API. The others are part of the internal API.
All of the units are composed together by an IoC container and make the library run. The IoC container's config is part of the public API, and developers can use it to change the behavior of any aspect of the library. In order to do this, developers need access to all public and internal units as well.
The core units are public and are available directly from the moq.ts package. Changes in those units follow Semantic Versioning, while changes in the internal units do not follow Semantic Versioning and could produce all types of version increments.
import * from "moq.ts/internal";
Internal API access provides wide opportunities to customize the library behavior. However, the user code that is based on the internal API could easily be broken by a new release.
If you need to make work instanceof operator or you need to deal with prototype of the mock object you can use prototypeof function of Mock class. Or you can use Object.getPrototypeOf or Object.setPrototypeOf functions on mocked object.
class TestObject implements ITestObject {
}
const mock = new Mock<ITestObject>()
.prototypeof(TestObject.prototype)
.object();
mock.object() instanceof TestObject;// true
If you need to replicate behaviour of an existing object you can reflect mock's interactions on the object.
class Origin {
public property = 0;
public method(input: number): number {
return input * this.property;
}
}
const origin = new Origin();
const mock = new Mock<Origin>()
.setup(() => It.IsAny())
.mimics(origin);
const mocked = mock.object();
mocked.property = 3;
const actual = mocked.method(2);
expect(actual).toBe(6);
mock.verify(instance => instance.method(2));
Some operations are not possible to trap in order to keep the language consistent, one of them is typeof. The type of the proxy object will be the same as the proxy target. So at the moment the only available options is to provider target option as a create mock parameter.
class Origin {
}
const origin = new Origin();
const mock = new Mock<Origin>({target: new Origin()});
expect(typeof mock.object()).toBe(typeof new Origin());
The library supports in operator. More examples could be found here
const name = "arbitrary name";
const object = new Mock<{}>()
.setup(instance => name in instance)
.returns(true)
.object();
expect(name in object).toBe(true);
interface ITestObject {
property: string;
method(): void;
}
class TestObject implements ITestObject {
property: string;
method(): void {
return undefined;
}
}
const object = new Mock<ITestObject>()
.prototypeof(TestObject.prototype)
.object();
// because "property" in new TestObject() === false
expect("property" in object).toBe(false);
expect("method" in object).toBe(true);
const mock = new Mock<{}>();
const object = mock.object();
const actual1 = "property" in object;
const actual2 = "method" in object;
mock.verify(instance => "property" in instance, Times.Once());
mock.verify(instance => "method" in instance, Times.Once());
The library supports new operator. More examples could be found here
class TestObject {
constructor(public readonly arg) {
}
}
it("Returns new object with callback", () => {
const value = "value";
const mock = new Mock<typeof TestObject>({target: TestObject})
.setup(instance => new instance(value))
.callback(({args: [name]}) => new TestObject(name));
const object = mock.object();
const actual = new object(value);
expect(actual).toEqual(new TestObject(value));
mock.verify(instance => new instance(value));
});
it("Returns new object with returns", () => {
const value = "value";
const expected = new TestObject(value);
const mock = new Mock<typeof TestObject>({target: TestObject})
.setup(instance => new instance(value))
.returns(expected);
const object = mock.object();
const actual = new object(value);
expect(actual).toBe(expected);
mock.verify(instance => new instance(value));
});
In some scenarios it is necessary to get Moq API from mocked object. For these purposes the library provides a predefined symbol MoqAPI. Mocked objects in their turn expose a symbol property to access to its Moq API.
Since this property makes sense only in context of the moq library and is not specific for mocked types it is not possible to define an interaction behaviour with Setup API.
The property is read only and trackabel, so it possible to use for verification.
const func = new Mock<() => void>()
.object();
func[MoqAPI]
.setup(instance => instance())
.returns(12);
const actual = func();
expect(actual).toBe(12);
In operator does not see this property until it is used in setups.
const object = new Mock<{}>()
.object();
expect(MoqAPI in object).toBe(false);
BUT
const mock = new Mock<ITestObject>();
const object = mock
.setup(instance => instance[MoqAPI])
.returns(undefined)
.object();
expect(MoqAPI in object).toBe(true);
expect(object[MoqAPI]).toBe(mock);
FAQs
Moq library for Typescript inspired by c# moq
We found that moq.ts demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
A malicious npm package targets Solana developers, rerouting funds in 2% of transactions to a hardcoded address.
Security News
Research
Socket researchers have discovered malicious npm packages targeting crypto developers, stealing credentials and wallet data using spyware delivered through typosquats of popular cryptographic libraries.
Security News
Socket's package search now displays weekly downloads for npm packages, helping developers quickly assess popularity and make more informed decisions.