Can ES6 constructors be stubbed more easily with Sinon? - testing

Given the (overly simplified) snippet:
import Validator from 'validator';
export default function isValid(arg) {
// Validator#isValid is an ES6 getter
return new Validator(arg).isValid;
}
How can I test that a Validator was instantiated with the given parameter? And stub isValid?
I know I can restructure my code to avoid the issue, I am not looking for a workaround as I found plenty (dependency injection, not using ES6 sugar, etc.).
I found a way, but it is terribly ugly. In test file:
import ValidatorNamespace from 'validator';
const Validator = ValidatorNamespace.default;
let validatorInstance;
let validatorConstructor;
const subject = arg => isValid(arg);
const validityStatus = true;
describe('isValid helper', () => {
beforeEach(() => {
validatorInstance = sinon.createStubInstance(Validator);
// Yay! This is how I managed to spy on the constructor!.. :(
validatorConstructor = sandbox.stub(ValidatorNamespace, 'default').
returns(validatorInstance);
sandbox.stub(validatorInstance, 'isValid').value(validityStatus);
});
it('instantiates the validator properly', ()=> {
subject('arg');
expect(validatorConstructor).to.have.been.calledWith('arg')
});
it('returns the value returned by the validator', ()=> {
expect(subject('arg')).to.eq(validityStatus);
});
});
Validator code:
export default class Validator {
constructor(arg) {
this.innerValue = arg;
}
get isValid() {
return aFunctionOf(this.innerValue);
}
}

What you want isn't really possible. Stubbing requires some kind of "seam" through which to put the stubs in place. When you import functions (constructors or otherwise) directly in your production code, the only seam you're leaving is the import process itself.
There is proxyquire, which overrides require calls in node. I don't know what environment you're using, and I don't really know how well this plays with ES6 modules. If you're transpiling to ES6 using babel, though, it should work.
In my experience this kind of stuff is not worth the additional complexity. My usual workaround is to just make a static factory function and stub/use that instead of using the constructor directly:
export default class Validator {
constructor(arg) {
this.innerValue = arg;
}
static create(arg) {
return new Validator(arg);
}
get isValid() {
return aFunctionOf(this.innerValue);
}
}
If you want a unit test for the factory, you can simply check the returned instance instead of stubbing the constructor:
it('create should return an instance', function() {
let arg = { foo: 'bar' };
let result = Validator.create(arg);
expect(result).to.be.an.instanceof(Validator);
expect(result.innerValue).to.equal(arg);
});

Related

Difference between watch and $watch

Just a simple question.
What is the difference between options and instance methods?
Based on the watch example, we can implement watcher as an option (https://v3.vuejs.org/api/options-data.html#watch) and a method of an instance (https://v3.vuejs.org/api/instance-methods.html#watch).
From my point of understanding, I can implement exactly the same feature with both methods and the only differences would be the syntax and the place of implementation.
If I am mistaken, can somebody explain to me based on example the difference between these two?
You are indeed (almost) correct with your assumption.
There is 2 major advantage of this.$watch() though.
You can start watching dynamically
the return-value of this.$watch() is an unwatch function with which you can dynamically stop the watcher during runtime
But that doesn't necessarly mean that you should always use this.$watch() over watch: {}. The opposite. You should always think about what your use case needs
Unwatch-example:
export default {
//..
created(props) {
const unwatchAge = this.$watch(() => this.user.age, (value, oldValue) => {
if (value >= 18) {
alert('You are now allowed to drive a car!');
unwatchAge(); //we don't need age watching for anything else
}
});
}
//...
}
BTW with VUE3 you might wanna look into the watch() / watchEffect() composition API methods.
watch() does the same as watch: {} and this.$watch() and also has an unwatch-method as return-value.
watchEffect() checks any value mentioned inside parameter (function) and puts a watcher on it internally.
watch() Example (composition)
import { toRef, watch} from 'vue';
export default {
//...
setup(props) {
const age = toRef(props.age);
const unwatchAge = watch(age, console.log);
// no () => age or () => age.value needed as age is a reference by using toRef and references can be handles like this
setTimeout(() => {
console.warn('unwatching age!');
unwatchAge();
}, 5000);
}
//...
}
watchEffect() Example (composition)
import { toRef, watchEffect} from 'vue';
export default {
//...
setup(props) {
const age = toRef(props.age);
watchEffect(() => {
if (age.value >= 18) {
alert('You are now allowed to drive a car!');
}
});
//vue will internally recognize that age has to be watched here. No telling it manually.
}
//...
}
The main difference from the docs it is that the instance method returns a unwatchable that you can trigger to stop watching a certain property:
const unwatchUsers = this.$watch('users', () => {});
setTimeout(unwatchUsers, 1000);
This is not possible with options API. It is extremely useful to use this unwatch returned by this.$watch when something happens in your app.
Have in mind what is the most appropriate to your use case and use it accordingly

Unit Testing the NestJS Routes are defined

I found this question about determine the routes. While the first answer is exactly what I need, and it works
import { Controller, Get, Request } from "#nestjs/common";
import { Request as ExpressRequest, Router } from "express";
#Get()
root(#Request() req: ExpressRequest) {
const router = req.app._router as Router;
return {
routes: router.stack
.map(layer => {
if(layer.route) {
const path = layer.route?.path;
const method = layer.route?.stack[0].method;
return `${method.toUpperCase()} ${path}`
}
})
.filter(item => item !== undefined)
}
}
I want to be able to unit test this.
My end to end test works fine
it('/api (GET) test expected routes', async done => {
const ResponseData = await request(app.getHttpServer())
.get('/api')
.set('Accept', 'application/json');
expect(ResponseData.status).toBe(200);
expect(ResponseData.headers['content-type']).toContain('json');
expect(ResponseData.body.routes.length).toBeGreaterThan(2);
done(); // Call this to finish the test
});
The problem I am having, is how to create and pass the Request part that is needed for the root() call for a unit test. The ExpressRequest is not a class or anything to simply create, and then assign values. It is currently a large definition. I assume there must be an easy way to create one, but I have not found it yet.
You can make use of the #golevelup/ts-jest package to help create mocks of objects. It can take an interface as a generic and return an entire jest mock that is compatible with the type.

Mocking runtime config value with sinon

I am adding some config values before hapi server start. Application is works fine although in test I can not use config.get(). I can get around with proxyquire. So I was wondering
Is adding config file "dynamically" is bad design?
Is there a way I can use config.get() in such suitation?
Any alternative approach?
//initialize.js
const config = require('config');
async function startServe() {
const someConfigVal = await callAPIToGetSomeJSONObject();
config.dynamicValue = someConfigVal;
server.start();
}
//doSomething.js
const config = require('config');
function doesWork() {
const valFromConfig = config.dynamicValue.X;
// In test I can use proxiquire by creating config object
...
}
function doesNotWork() {
const valFromConfig = config.get('dynamicValue.X');
// Does not work with sinon mocking as this value does not exist in config when test run.
// sinon.stub(config, 'get').withArgs('dynamicValue.X').returns(someVal);
.....
}
Context: testing.
Is adding config file "dynamically" is bad design? => No. I have done it before. The test code changes configuration file: default.json in mid test to check whether function under test behaves as expected. I used several config utilities.
Is there a way I can use config.get() in such suitation? => Yes. For sinon usage, see the example below, which use mocha. You need to define the stub/mock before the function under test use it, and do not forget to restore the stub/mock. Also there is official documentation related to this: Altering configuration values for testing at runtime, but not using sinon.
const config = require('config');
const sinon = require('sinon');
const { expect } = require('chai');
// Example: simple function under test.
function other() {
const valFromConfig = config.get('dynamicValue.X');
return valFromConfig;
}
describe('Config', function () {
it ('without stub or mock.', function () {
// Config dynamicValue.X is not exist.
// Expect to throw error.
try {
other();
expect.fail('expect never get here');
} catch (error) {
expect(error.message).to.equal('Configuration property "dynamicValue.X" is not defined');
}
});
it('get using stub.', function () {
// Create stub.
const stubConfigGet = sinon.stub(config, 'get');
stubConfigGet.withArgs('dynamicValue.X').returns(false);
// Call get.
const test = other();
// Validate te result.
expect(test).to.equal(false);
expect(stubConfigGet.calledOnce).to.equal(true);
// Restore stub.
stubConfigGet.restore();
});
it('get using mock.', function () {
// Create mock.
const mockConfig = sinon.mock(config);
mockConfig.expects('get').once().withArgs('dynamicValue.X').returns(false);
// Call get.
const test = other();
// Validate te result.
expect(test).to.equal(false);
// Restore mock.
expect(mockConfig.verify()).to.equal(true);
});
});
Hope this helps.

Vuex-module-decorator, modifying state inside an action

Using the vuex-module-decorator I have a authenticate action that should mutate the state.
#Action
public authenticate(email: string, password: string): Promise<Principal> {
this.principal = null;
return authenticator
.authenticate(email, password)
.then(auth => {
const principal = new Principal(auth.username);
this.context.commit('setPrincipal', principal);
return principal;
})
.catch(error => {
this.context.commit('setError', error);
return error;
});
}
// mutations for error and principal
But this fail with the following message:
Unhandled promise rejection Error: "ERR_ACTION_ACCESS_UNDEFINED: Are you trying to access this.someMutation() or this.someGetter inside an #Action?
That works only in dynamic modules.
If not dynamic use this.context.commit("mutationName", payload) and this.context.getters["getterName"]
What I don't understand is that it works well with #MutationAction and async. However I miss the return type Promise<Principal>.
#MutationAction
public async authenticate(email: string, password: string) {
this.principal = null;
try {
const auth = await authenticator.authenticate(email, password);
return { principal: new Principal(auth.username), error: null };
} catch (ex) {
const error = ex as Error;
return { principal: null, error };
}
}
--
At this time I feel blocked and would like to have some help to implement an #Action that can mutate the state and return a specific type in a Promise.
Just add rawError option to the annotation so it becomes
#Action({rawError: true})
And it display error normally. this is because the the library "vuex-module-decorators" wrap error so by doing this you will able to get a RawError that you can work with
You can vote down this answer if you would like because it isn't answering the specific question being posed. Instead, I am going to suggest that if you are using typescript, then don't use vuex. I have spent the past month trying to learn vue /vuex and typescript. The one thing I am committed to is using typescript because I am a firm believer in the benefits of using typescript. I will never use raw javascript again.
If somebody would have told me to not use vuex from the beginning, I would have saved myself 3 of the past 4 weeks. So I am here to try and share that insight with others.
The key is Vue 3's new ref implementation. It is what really changes the game for vuex and typescript. It allows us to not have to rely on vuex to automatically wrap state in a reactive. Instead, we can do that ourselves with the ref construct in vue 3. Here is a small example from my app that uses ref and a typescript class where I was expecting to use vuex in the past.
NOTE1: the one thing you lose when using this approach is vuex dev tools.
NOTE2: I might be biased as I am ported 25,000 lines of typescript (with 7000 unit tests) from Knockout.js to Vue. Knockout.js was all about providing Observables (Vue's ref) and binding. Looking back, it was kind of ahead of its time, but it didn't get the following and support.
Ok, lets create a vuex module class that doesn't use vuex. Put this in appStore.ts. To simplify it will just include the user info and the id of the club the user is logged into. A user can switch clubs so there is an action to do that.
export class AppClass {
public loaded: Ref<boolean>;
public userId: Ref<number>;
public userFirstName: Ref<string>;
public userLastName: Ref<string>;
// Getters are computed if you want to use them in components
public userName: Ref<string>;
constructor() {
this.loaded = ref(false);
initializeFromServer()
.then(info: SomeTypeWithSettingsFromServer) => {
this.userId = ref(info.userId);
this.userFirstName = ref(info.userFirstName);
this.userLastName = ref(info.userLastName);
this.userName = computed<string>(() =>
return this.userFirstName.value + ' ' + this.userLastName.value;
}
}
.catch(/* do some error handling here */);
}
private initializeFromServer(): Promise<SomeTypeWithSettingsFromServer> {
return axios.get('url').then((response) => response.data);
}
// This is a getter that you don't need to be reactive
public fullName(): string {
return this.userFirstName.value + ' ' + this.userLastName.value;
}
public switchToClub(clubId: number): Promise<any> {
return axios.post('switch url')
.then((data: clubInfo) => {
// do some processing here
}
.catch(// do some error handling here);
}
}
export appModule = new AppClass();
Then when you want to access appModule anywhere, you end up doing this:
import { appModule } from 'AppStore';
...
if (appModule.loaded.value) {
const userName = appModule.fullName();
}
or in a compositionApi based component. This is what would replace mapActions etc.
<script lang="ts">
import { defineComponent } from '#vue/composition-api';
import { appModule } from '#/store/appStore';
import footer from './footer/footer.vue';
export default defineComponent({
name: 'App',
components: { sfooter: footer },
props: {},
setup() {
return { ...appModule }
}
});
</script>
and now you can use userId, userFirstName, userName etc in your template.
Hope that helps.
I just added the computed getter. I need to test if that is really needed. It might not be needed because you might be able to just reference fullName() in your template and since fullName() references the .value variables of the other refs, fullName might become a reference itself. But I have to check that out first.
I sugest this simple solution, work fine for me 👌:
// In SomeClassComponent.vue
import { getModule } from "vuex-module-decorators";
import YourModule from "#/store/YourModule";
someMethod() {
const moduleStore = getModule(YourModule, this.$store);
moduleStore.someAction();
}
If the action has parameters, put them.
Taken from: https://github.com/championswimmer/vuex-module-decorators/issues/86#issuecomment-464027359

Aurelia Validation with i18n?

Has anyone gotten Aurelia Validation to work with the i18n Plugin for multi-lingual error messages? My app won't even start when I add in the code from the Aurelia documentation http://aurelia.io/hub.html#/doc/article/aurelia/validation/latest/validation-basics/12.
Here's my main.js:
import environment from './environment';
import {I18N} from 'aurelia-i18n';
import XHR from 'i18next-xhr-backend';
import {ValidationMessageProvider} from 'aurelia-validation';
//Configure Bluebird Promises.
//Note: You may want to use environment-specific configuration.
Promise.config({
warnings: {
wForgottenReturn: false
}
});
export function configure(aurelia) {
aurelia.use
.standardConfiguration()
.feature('resources')
.plugin('aurelia-validation');
aurelia.use.plugin('aurelia-i18n', (instance) => {
// register backend plugin
instance.i18next.use(XHR);
// adapt options to your needs (see http://i18next.com/docs/options/)
instance.setup({
backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json',
},
lng : 'en',
ns: ['translation'],
defaultNS: 'translation',
attributes : ['t','i18n'],
fallbackLng : 'en',
debug : false
});
});
// Straight from Aurelia Documentation
const i18n = aurelia.container.get(i18n);
ValidationMessageProvider.prototype.getMessage = function(key) {
const translation = i18n.tr(`errorMessages.${key}`);
return this.parser.parseMessage(translation);
};
// Straight from Aurelia Documentation
ValidationMessageProvider.prototype.getDisplayName = function(propertyName) {
return i18n.tr(propertyName);
};
if (environment.debug) {
aurelia.use.developmentLogging();
}
if (environment.testing) {
aurelia.use.plugin('aurelia-testing');
}
aurelia.start().then(() => aurelia.setRoot());
}
The error I get is vendor-bundle.js:3394 Error: key/value cannot be null or undefined. Are you trying to inject/register something that doesn't exist with DI?(…)
If I delete the two sections marked // Straight from Aurelia Documentation, it works fine (but only in one language).
If you see an error in my code, please point it out. Or, if you have a working example using aurelia-validation and aurelia-i18n working together, please pass on a link. Thanks!
Ran into this issue as well. It appears that the line
// Straight from Aurelia Documentation
const i18n = aurelia.container.get(i18n);
is getting (or more likely creating) a different instance of i18n than the
aurelia.use.plugin('aurelia-i18n', (instance) =>
I fixed this by getting the i18n instance directly from the aurelia.use.plugin() as follows (this is typescript but same principle applies to pure js):
let i18n:I18N = null;
aurelia.use.plugin('aurelia-i18n', (instance:I18N) => {
i18n = instance;
//rest of plugin code here
}
Use the imported I18N instead:
const i18n = aurelia.container.get(I18N);
But indeed, i18n seems to stop working afterward. My solution was to update the i18n singleton instance in the first page (app.js), the first time it gets injected:
constructor(i18n) {
this.i18n = i18n;
this.initAureliaSingletons();
}
/**
* Some configurations breaks in 'main.js'
* singletons can be configure here
* #return {void}
*/
initAureliaSingletons() {
const i18n = this.i18n;
ValidationMessageProvider.prototype.getMessage = function(key) {
const translation = i18n.tr(`validation-${key}`);
return this.parser.parseMessage(translation);
};
}
I put it on my main and it works. I think that the trick was to use the variable that was initialized in the plug-in initialization:
var i18n;
aurelia.use.plugin('aurelia-i18n', (instance) => {
// register backend plugin
instance.i18next.use(Backend.with(aurelia.loader)).use(LngDetector);
i18n = instance;
(...)
aurelia.use.plugin('aurelia-validation');
var standardGetMessage = ValidationMessageProvider.prototype.getMessage;
ValidationMessageProvider.prototype.getMessage = function (key) {
if (i18n.i18next.exists(key)) {
const translation = i18n.tr(key);
return this.parser.parse(translation);
} else {
return standardGetMessage(key);
}
};
ValidationMessageProvider.prototype.getDisplayName = function (propertyName, displayName) {
if (displayName !== null && displayName !== undefined) {
return displayName;
}
return i18n.tr(propertyName);
};