I am writing Jasmine tests for my Javascript application. A major time-sink has been testing my code which depends on StripeCheckout. Stripe does not want you to use it offline. I realized I should mock the service, but that has not been easy in Jasmine.
How can I mock "custom" (instead of "simple") usage of StripeCheckout?
I tried to use spies, like so,
var StripeCheckout = jasmine.createSpyObj('StripeCheckout', ['configure']);
But I think the created object needs to attach to the global object (window).
So, I can add an object to the global
object. This worked,
but it feels lame.
Another option could be to tell
Karma to load the
page over the network. This worked for me, but it seems lame to make
a network request for tests.
I'm not sure if you have figured this out yet but an easy way to do this would be to just create a simple mocked implementation for StripeCheckout. Something like this should work.
beforeEach(function() {
module(function($provide) {
$provide.factory('StripeCheckout', function() {
//your mocked custom implementation goes here
configure: function() {
//do something
}
}
}
inject(function($injector) {
StripeCheckoutMock = $injector.get('StripeCheckout');
}
)
This way you are not making a request in order to run your tests. The test will just use the mocked service that you have set up.
Related
I have a pretty standard application with a frontend, a backend and some options in the frontend for modifying data. My backend fires events when data is modified (eg. record created, record updated, user logged in, etc.).
Now what I want to do is for my customers to be able to code their own functions and "hook" them into these events.
So far the approaches I have thought of are:
Allowing users in the frontend to write some code in a codeeditor like codemirror, but this whole storing code and executing it with some eval() seems kind of risky and unstable.
My second approach is illustrated below (to the best of my ability at least). The point is that the CRUD API calls a different "hook" web service that has these (recordUpdated, recordCreated, userLoggedIn,...) hook methods exposed. Then the client library needs to extend some predefined interfaces for the different hooks I expose. This still seems doable, but my issue is I can't figure out how my customers would deploy their library into the running "hook" service.
So it's kind of like webhooks, except I already know the exact hooks to be created which I figured could allow for an easier setup than customers having to create their own web services from scratch, but instead just create a library that is then deployed into an existing API (or something like that...). Preferably the infrastructure details should be hidden from the customers so they can focus solely on making business logic inside their custom hooks.
It's kind of hard to explain, but hopefully someone will get and can tell me if I'm on the right track or if there is a more standard way of doing hooks like these?
Currently the entire backend is written in C# but that is not a requirement.
I'll just draft out the main framework, then wait for your feedback to fill in anything unclear.
Disclaimer: I don't really have expertise with security and sandboxing. I just know it's an important thing, but really, it's beyond me. You go figure it out 😂
Suppose we're now in a safe sandbox where all malicious behaviors are magically taken care, let's write some Node.js code for that "hook engine".
How users deploy their plugin code.
Let's assume we use file-base deployment. The interface you need to implement is a PluginRegistry.
class PluginRegistry {
constructor() {
/**
The plugin registry holds records of plugin info:
type IPluginInfo = {
userId: string,
hash: string,
filePath: string,
module: null | object,
}
*/
this.records = new Map()
}
register(userId, info) {
this.records.set(userId, info)
}
query(userId) {
return this.records.get(userId)
}
}
// plugin registry should be a singleton in your app.
const pluginRegistrySingleton = new PluginRegistry()
// app opens a http endpoint
// that accepts plugin registration
// this is how you receive user provided code
server.listen(port, (req, res) => {
if (isPluginRegistration(req)) {
let { userId, fileBytes, hash } = processRequest(req)
let filePath = pluginDir + '/' + hash + '.js'
let pluginInfo = {
userId,
// you should use some kind of hash
// to uniquely identify plugin
hash,
filePath,
// "module" field is left empty
// it will be lazy-loaded when
// plugin code is actually needed
module: null,
}
let existingPluginInfo = pluginRegistrySingleton.query(userId)
if (existingPluginInfo.hash === hash) {
// already exist, skip
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('ok');
} else {
// plugin code written down somewhere
fs.writeFile(filePath, fileBytes, (err) => {
pluginRegistrySingleton.register(userId, pluginInfo)
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('ok');
})
}
}
})
From the perspective of hook engine, it simply opens a HTTP endpoint to accept plugin registration, agnostic to the source.
Whether it's from CI/CD pipeline, or plain web interface upload, it doesn't matter. If you have CI/CD setup for your user, it is just a dedicated build machine that runs bash scripts isn't it? So just fire a curl call to this endpoint to upload whatever you need. Same applies to web interface.
How we would execute plugin code
User plugin code is just normal Node.js module code. You might instruct them to expose certain API and conform to your protocol.
class HookEngine {
constructor(pluginRegistry) {
// dependency injection
this.pluginRegistry = pluginRegistry
}
// hook
oncreate(payload) {
// hook call payload should identify the user
const pluginInfo = this.pluginRegistry.query(payload.user.id)
// lazy-load the plugin module when needed
if (pluginInfo && !pluginInfo.module) {
pluginInfo.module = require(pluginInfo.filePath)
}
// user plugin module is just normal Node.js module
// you load it with `require`, and you call what ever function you need.
pluginInfo.module.oncreate(payload)
}
}
I am trying to test the following bit of code:
DimonaClient is just a simple wrapper around a Laravel HttpClient; simplified function here:
The getDeclaration() response is a \Illuminate\Http\Client\Response
What I am trying to do in my test is:
Mock the DimonaClient class so I don't create an actual api call
"Mock" (use Laravel's Http::response()) the response I want so that I can test that a 200 w/ certain statuses dispatches the appropriate event (also mocked, but not relevant here)
My test code looks like this:
My issue(s) seem to be:
the getDeclaration() has an expectation of Illuminate\Http\Client\Response but I can't seem to create anything that will satisfy that (a new Response wants a MessageInterface, etc, etc... )
I don't actually need getDeclaration() to return anything for my testing, so I wonder if I should be mocking this differently in any case (I base this assumption on Http::response handling the internal code I'm testing for things like $response->ok(), instead of a Mockery expectation)
I feel like I'm one small step away from making this work, but going round in circles trying to hook it up correctly.
TIA!
If you are using Http Facade, you don't need to mock DimonaCient. You are nearly there with your test, but let me show you what you would have done:
/** #test */
public function it_can_handle_an_approved_submission(): void
{
Http::fake([
'*' => Http::response([
'declarationStatus' => [
'result' => DimonaDeclarationStatus::ACCEPTED,
'dimonaPeriodId' => $this->faker->numerify('############'),
],
],
]);
$dimonaDeclarationId = $this->faker->numerify('############');
// Do your normal call, and then assertions
}
Doing this, you will tell Http to fake any URL, because we are using *. I would recommend you use $this->endpoint/$declarationId so if it does not match, you will also know you did not hit the right endpoint.
I am not sure what Laravel you are using but this is available since Laravel 6+, check Http fake URLs.
I have a button that may or may not be disabled via a switch next to it. I know it's state, when the test gets to it, but I feel like I should be checking it anyway and switching it if it isn't in the state I want.
How can I write a conditional for this in a cypress test?
if (testSubject.getButton().is("disabled) {
// do stuff
} else {
// do other stuff
}
The general pattern for that is to use jQuery in your if statement,
cy.get('my-button')
.then($button => {
if ($button.is(':enabled')) {
cy.wrap($button).click()
}
})
Be aware that using conditional testing like this can mask a regression in the app that allows a bug to slip through. It's ok if you have other tests covering it.
I have mixed application that uses Apollo for both React and non-react code.
However, I can’t find documentation or code examples around testing non-react code with the apollo client,not using MockedProvider. I did, however, notice that apollo exports a mock client from the testing directory.
import { createMockClient } from '#apollo/client/testing';
I haven’t found any documentation about this API and am wondering if it’s intended to be used publicly and, if not, what the supported approach is for this.
The reason I need this is simple: When using Next.js’ SSR and/or SSG features data fetching and actual data rendering are split into separate functions.
So the fetching code is not using React, but Node.js to fetch data.
Therefore I use apolloClient.query to fetch the data I need.
When trying to wrap a react component around that fetching code in a test an wrap MockedProvider around that the apolloClient’s query method always returns undefined for mocked queries - so it seems this only works for the useQuery hook?
Do you have any idea how to mock the client in non-react code?
Thank you for your support in advance. If you need any further information from me feel free to ask.
Regards,
Horstcredible
I was in a similar position where I wanted to use a MockedProvider and mock the client class, rather than use useQuery as documented here: https://www.apollographql.com/docs/react/development-testing/testing/
Though it doesn't seem to be documented, createMockClient from '#apollo/client/testing' can be passed the same mocks as MockedProvider to mock without useQuery. These examples assume you have a MockedProvider:
export const mockGetAssetById = async (id: Number): Promise<any> => {
const client = createMockClient(mocks, GetAsset)
const data = await client.query({
query: GetAsset,
variables: id,
})
return data
}
Accomplishes the same as:
const { data } = useQuery(
GetAsset,
{ variables: { id } }
)
I started using Protractor and the first thing I've tried to do is to use Mocha and Chai instead of Jasmine. Although now I'm not sure if that was a good idea.
first I needed to make Chai accessible from all the spec files, without having to import everytime, I found it's possible to do in protractor.conf file:
onPrepare: ->
global.chai = require 'chai'
chai.use require 'chai-string'
chai.use require 'chai-as-promised'
global.expect = chai.expect
now in a spec like this:
it "when clicked should sort ",->
headerColumns.get(0).click()
firstCellText = $$(".first-cell").getText()
secondCellText = $$(".second-cell").getText()
# this won't work
expect(firstCellText).eventually.be.above(secondCellText)
to make it work I could do:
# now this works
$$(".second-cell").getText().then (secondCellText)->
expect(firstCellText).eventually.be.above(secondCellText)
but that's ugly, and I don't want to wrap stuff inside of .then all the time. I'm thinking there should be a better way(?)
I was having the same problem. The issue for me was to add a longer timeout to mocha through the Protractor config.js.
This works because Protractor tests take considerably longer relative to other modules like supertest since an actual browser is being interacted with.
I added
mochaOpts: {
timeout: 5000
}
to my protractor config and the tests passed.
Either you need to mention timeout for the whole test suite using "this.timeout(1000);" just below the describe block, or you can change it for individual test cases by explicitly defining timeout for each "it" block.
example for describe block:
describe("test-suite",function () {
this.timeout(5000);
it("test-case",function () {
//test case code goes here
});
});
example for it block:
it("test-case",function () {
this.timeout("20000");
});
I found this question while I was trying to do the same thing in TypeScript, but the approach in the protractor.conf.js is identical.
Making chai available globally
It appears to achieve this you're right that it needs to be done in the on prepare. a fairly terse example is below. As I understand it this is needed because chai is of course not apart of mocha but some additional candy that we can use with mocha. Unlike jasmine where everything is bundled into the framework.
protractor.conf.js fragment
onPrepare: function() {
var chai = require('chai'); // chai
var chaiAsPromised = require("chai-as-promised"); // deal with promises from protractor
chai.use(chaiAsPromised); // add promise candy to the candy of chai
global.chai = chai; // expose chai globally
}
example spec
Once you've got chai and chai-as-promised setup globally you need to add a "little" boiler plate to your spec to expose expect which comes from chai.
example.e2e-spec.ts fragment
const expect = global['chai'].expect; // obviously TypeScript
it('will display its title', () => {
pageObject.navigateTo();
const title = element(by.css('app-root h1')).getText();
expect(title).to.eventually.contain('An awesome title');
});
Avoiding then
I can't tell what your $$ references are, but if you're using the protractor components browser and by etc. then things clean up a little bit.
The call const title = element(by.css('app-root h1')).getText(); seems to return a promise which jasmine seems to deal with out of the box while mocha+chai doesn't. That's where chai-as-promised comes in.
using our additional syntax candy expect(title).to.eventually.contain('An awesome title'); resolves the promise quite neatly and we've avoided all those then calls, but we do need the eventually.
I've provided you a link to a working TypeScript example that might help demonstrate.