How can I mock aws-sdk with jest? - class

I am trying to mock aws-sdk with jest. Actually I only care about one function. How can I do this? I have read the docs about mocking classes with jest, but the docs are complicated and I don't quite understand them.
Here is my best attempt:
handler.test.js
'use strict';
const aws = require('aws-sdk');
const { handler } = require('../../src/rotateSecret/index');
jest.mock('aws-sdk');
const event = {
SecretId: 'test',
ClientRequestToken: 'ccc',
Step: 'createSecret',
};
describe('rotateSecret', () => {
it.only('should not get or put a secret', async () => {
aws.SecretsManager.mockImplementation(() => ({
getSecretValue: () => ({}),
}));
expect.assertions(1);
await handler(event);
// You can see what I am trying to do here but it doesn't work
expect(aws.SecretsManager.getSecretManager).not.toHaveBeenCalled();
});
});
handler.js
exports.handler = async (event) => {
const secretsManager = new aws.SecretsManager();
const secret = await secretsManager.describeSecret({ SecretId: event.SecretId }).promise();
if (someCondition) {
console.log("All conditions not met");
return;
}
return secretsManager.getSecretValue(someParams)
};

Okay so the way I would approach this is as follows:
AWS-SDK Mock
Create an actual mock for aws-sdk and put it in __mocks__/aws-sdk.js file at the root of your project
// __mocks__/aws-sdk.js
class AWS {
static SecretsManager = class {
describeSecret = jest.fn(() =>{
return { promise: ()=> Promise.resolve({ ARN: "custom-arn1", Name: "describeSec" })}
});
getSecretValue = jest.fn(() =>{
return {promise: ()=> Promise.resolve({ ARN: "custom-arn2", Name: "getSecretVal" })
});
};
}
module.exports = AWS;
I have used static before SecretsManager because AWS class is never instantiated yet it wants access to SecretsManager class.
Inside SecretsManager, I have defined 2 functions and stubbed them using jest.fn.
Now same stuff as you have done in your test file:
jest.mock('aws-sdk');
How to Test
To test if your mock functions are called, thats the tricky part (so i will detail that at the end of this post).
Better approach would be to assert against the end result of your main function after all processing is finished.
Assertions
Back in your test file, I would simply invoke the handler with the await (as you already have) and then assert against the final result like so:
// test.js
describe("rotateSecret", () => {
it.only("should not get or put a secret", async () => {
const event = {name:"event"};
const result = await handler(event);
expect(result).toEqual("whatever-your-function-is-expected-to-return");
});
});
Testing Secret Manager's function invocations
For this you will need to tweak your main handler.js file itself and will need to take out invocation of secrets Manager from the main function body like so:
const secretsManager = new aws.SecretsManager(); // <---- Declare it in outer scope
exports.handler = async (event) => {
const secret = await secretsManager
.describeSecret({ SecretId: event.SecretId })
.promise();
if (someCondition) {
console.log("All conditions not met");
return;
}
return secretsManager.getSecretValue(someParams);
};
Then back in your test.js file, you will need to similarly declare the SecretsManager invocation before you initiate your handler function like so:
//test.js
describe("rotateSecret", () => {
const secretsManager = new aws.SecretsManager(); // <---- Declare it in outer scope
it.only("should not get or put a secret", async () => {
const event = {name:"event"};
await handler(event);
// Now you can make assertions on function invocations
expect(secretsManager.describeSecret).toHaveBeenCalled();
// OR check if passed args were correct
expect(secretsManager.describeSecret).toHaveBeenCalledWith({
SecretId: event.SecretId,
});
});
});
This will allow you to make assertions on function invocation as well the args that were passed.
The reason I declare it outside function scope is to tell Jest that secretsManager should be existing somewhere in global scope and it should be used from there.
Previously, we had it declared inside the function scope, so Jest would invoke it but we weren't able to get access to it.
We couldn't directly reference it like this AWS.SecretsManager.getSecretManager because getSecretManager method is only available after you instantiate the SecretsManager class (and even if you did that, you will get a new instance of the class which won't help with any assertions).
Downside of __mocks__/aws.js fake module
Obvious issue is - you are stubbing the function on every single call and maybe you won't want that.
Perhaps you only want to stub it out once for a specific test but for the rest of them you want it to run normal.
In that case, you should not create __mocks__ folder.
Instead, create a one-time fake BUT make sure your SecretsManager invocation is in the outside scope in your test file as before.
//test.js
const aws = require("aws-sdk");
describe("rotateSecret", () => {
// Declare it in outer scope
const secretsManager = new aws.SecretsManager();
it.only("should not get or put a secret", async () => {
const event = {name:"event"};
// Create a mock for this instance ONLY
secretsManager.describeSecret = jest.fn().mockImplementationOnce(()=>Promise.resolve("fake-values"));
await handler(event);
expect(secretsManager.describeSecret).toHaveBeenCalled();
expect(secretsManager.describeSecret).toHaveBeenCalledWith({
SecretId: event.SecretId,
});
});
});

Related

How to test Function passed as an argument in Flutter?

How to test Function passed as an argument in Flutter?
code:
Future<User> execute({
required String username,
required String password,
required void Function(AuthFailure fail) onFailure,
required void Function(User user) onSuccess,
}) async {
if (username.isNonValid || password.isNonValid) {
onFailure(const AuthFailure.wrongCredentials()); // I want to test this line
return const User.anonymous();
}
...
}
test:
test('use case - failure execution for incorrect credentials', () async {
// GIVEN
// WHEN
final user = await useCase.execute(
username: "noname",
password: "password",
onFailure: (fail) {},
onSuccess: (user) {},
);
// THEN
// TODO how to verify onFailure call inside useCase?
expect(user, const User.anonymous());
});
Or maybe testing this way is not the idiomatic way, because the test becomes more white-box instead black-box? Should I perceive passing functions as arguments to use cases as anti-pattern? I can change it then. The proposition is to return sth like Either from useCase.execute():
Future<Either<Failure, Success>> execute({
required String username,
required String password,
}) async {
if (username.isEmpty || password.isEmpty) {
// return wrapper around AuthFailure.wrongCredentials()) of Either left subtype (Either has two subtypes)
}
...
}
This way I only verify return type, and all the lines are covered this way. It's gonna work, but I feel better with the simplest, not the smartest solution.
PS I use Mocktail for mocking, but using Mockito in solution is also warmly welcomed.
If you just want to verify that the callback is triggered, I personally would just make your callback set a flag and then test that flag afterward, which I think is straightforward, simple, and easy to understand with no magic:
test('use case - failure execution for incorrect credentials', () async {
var failureCalled = false;
final user = await useCase.execute(
username: "noname",
password: "password",
onFailure: (fail) => failureCalled = true,
onSuccess: (user) {},
);
expect(user, const User.anonymous());
expect(failureCalled, true);
});
But if you really want to use Mocks, you will need some Mock object to use and to call instance methods on that in callbacks. With Mockito you could do:
test('use case - failure execution for incorrect credentials', () async {
dynamic mock = Mock();
final user = await useCase.execute(
username: "noname",
password: "password",
onFailure: (fail) => mock.fail(fail),
onSuccess: (user) {},
);
expect(user, const User.anonymous());
verify(mock.fail(any)).called(1);
});
Some things to note:
To avoid declaring a class with the expected instance methods and then code-generating stubs, create a raw Mock instance but declare it as dynamic to disable static type-checking. This will then take advantage the Mock.noSuchMethod implementation.
You can't use onFailure: mock.fail directly since the Mock has no generated stubs, and mock.fail will just be null instead of a Function.
I am not experienced with Mocktail, but I imagine that you could do something similar.

Suspense returns data too fast in #tanstack/react-query v4

I am upgrading a React app from react-query v3 to #tanstack/react-query v4.
Almost everything works, but I'm having a problem with Suspense.
I have a react component:
const WrapperPageEdit: React.FC<MyProps> = ({
pageUuid,
redirect,
}: MyProps) => {
const FormPage = React.lazy(() => import('./FormPage'));
const { data } = usePageView(pageUuid);
if (data?.[0]) {
const pageObjectToEdit= data[0];
const content = pageObjectToEdit.myStuff.content;
return (
<Suspense
fallback={<Trans id="loading.editor">Loading the editor...</Trans>}
>
<FormPage
id={uuid}
content={content}
redirect={redirect}
/>
</Suspense>
);
}
return <p>No data.</p>;
};
And here's my query:
export function usePageView(
uuid: string,
): UseQueryResult<DrupalPage[], Error> {
return useQuery<DrupalPage[], Error>(
queryKeyUsePageView(uuid),
async () => {
return fetchAnon(getPageByPageUuid(uuid));
},
{
cacheTime: YEAR_MILLISECONDS,
staleTime: YEAR_MILLISECONDS,
onSuccess: (data) => {
if (data?.[0]) {
data.map((element) => processResult(element));
}
},
},
);
}
This works in v3 but fails in v4 with the following error:
TypeError: Cannot read properties of undefined (reading 'content')
The reason the property is undefined is because that property is set by the processing in onSuccess (data.map).
The issue appears to be that in v4, the component WrapperPageEdit is refreshed before onSuccess in the usePageView query has finished processing, whereas in v3, the component WrapperPageEdit is not refreshed until the onSuccess data.map is complete.
How can I correctly fix this? I can write some additional code to try to check whether the onSuccess data.map is complete, but since react-query handled this automatically in v3, I'd like to rewrite my code in v4 so that it is the same.
The problem is likely that you are mutating the data in onSuccess. Directly modifying data in callbacks is not a good idea. Instead, do your transformation for example directly in the queryFn:
async () => {
const data = fetchAnon(getPageByPageUuid(uuid));
if (data?.[0]) {
data.map((element) => processResult(element));
}
return data
},
other good places to do data transformation is e.g. the select option, but it should always happen in an immutable way, because otherwise, you are overwriting the cached data inadvertently. React prefers updates to be immutable.

How do I define an action without having to implement a reducer for it?

I'm converting some existing redux code to the toolkit way. We have a lot of actions that trigger thunks (to load data from backend) but dont have a reducer. Our pattern being the load/success/fail triple. Basically only the success and fails need a reducer statement. How do I do this with the toolkit? Do I have to put in a reducer that just returns the unchanged state for the load actions?
With redux-toolkit you have a few options here...
1. Existing thunks + RTK actions
If you only need to update one slice of your store with the loaded data, you can create “success” and “fail” actions in the reducers property on that slice. Then, change your thunk to dispatch those instead of the old success/fail actions.
const slice = createSlice({
name: 'data',
initialState: {},
reducers: {
fetchDataSuccess(state, action) {
// Do something with the response
},
fetchDataError(state, action) {
// Do something with the error
}
}
}
const { fetchDataSuccess, fetchDataError } = slice.actions
export function fetchData() {
return dispatch => api.getData()
.then(response => dispatch(fetchDataSuccess(response.data)))
.catch(error => dispatch(fetchDataError(error))
}
export default slice.reducer
2. Existing thunks + extraReducers
If you don't want to refactor the existing thunk, or if the actions will be used across multiple slices, you can use the extraReducers property.
// These can also be defined in a separate file and imported
const FETCH_SUCCESS = 'data/FETCH_SUCCESS'
const FETCH_FAIL = 'data/FETCH_FAIL'
export function fetchData() {
return dispatch => api.getData()
.then(response => dispatch({ type: FETCH_SUCCESS, payload: response.data }))
.catch(error => dispatch({ type: FETCH_FAIL, payload: error }))
}
const slice = createSlice({
// ... the usual properties
extraReducers: {
[FETCH_SUCCESS](state, action) {
// Do something with the response
},
[FETCH_FAIL](state, action) {
// Do something with the error
}
}
}
3. createAsyncThunk
This approach is similar to the above, but the createAsyncThunk utility handles a lot of it for you, like catching errors, dispatching the actions at the right time, etc.
const fetchData = createAsyncThunk(
'data/fetchData',
() => api.getData().then(response => response.data)
)
const slice = createSlice({
// ... the usual properties
extraReducers: {
[fetchData.fulfilled](state, action) {
// Do something with the response
},
[fetchData.rejected](state, action) {
// Do something with action.error
}
}
}
// Components still call this like a normal function: fetchData()
export { fetchData }
export default slice.reducer
Whichever way you end up going, if you're not using the "load" action (or .pending from createAsyncThunk), you don't need to add it to either reducers or extraReducers.
I think you can simply create thunk-actions.ts (or eg. saga-actions.ts) file to keep actions that trigger data loading.
import { createAction } from '#reduxjs/toolkit';
export const fetchUserComments = createAction<{ id: string }>(
'fetchUserComments',
);
all actions that have reducer's logic will be generated by slice

Update clientId after initializing Google Actions SDK for NodeJS

I'm using the account linking feature for Actions SDK and following the guide here (https://developers.google.com/assistant/identity/google-sign-in#start_the_authentication_flow)
It shows the initialization like this
const app = actionssdk({
// REPLACE THE PLACEHOLDER WITH THE CLIENT_ID OF YOUR ACTIONS PROJECT
clientId: CLIENT_ID,
});
But for my use case, I'll read the clientId from DB which is stored against the projectId of the project. I can extract the projectId only after the MAIN intent is triggered.
My question is, how can I set the clientId after initializing actionssdk?
This solution uses the new Actions SDK, but the principal is the same for the legacy SDK as well:
const {
conversation,
Canvas,
} = require('#assistant/conversation');
const functions = require('firebase-functions');
const wrapper = async (req, res) => {
// You can get any data you need here:
const myAsyncBootstrapData = await getData();
const app = conversation({debug: true, ...myAsyncBootstrapData});
app.handle('welcome', (conv) => {
conv.add('This is a demo.');
});
return app(req, res);
};
exports.ActionsOnGoogleFulfillment = functions.https.onRequest(wrapper);
functions.https.onRequest accepts any callable, including ones that return promises. If you need to block while loading configuration data asynchronously, you can do so by wrapping your definition in an async function.
I found a simple solution to this. I am adding it here for future references.
// handler.js
async function handleRequest(req, res) {
const clientId = // retrieve the clienId using your business logic
const app = actionssdk({
clientId: clientId
})
}
module.exports = handleRequest;
Instead of directly creating an instance of actionssdk, wrap it inside a function like this.
// index.js
const handler = require('./path/to/hander.js');
app.post('/webhook', handler);
Then when defining the webhook, use the wrapper function to handle the webhook requests

How to call a customhelper in CodeCeptjs?

How to call a method defined in customhelper?
MycustomHelper
const { Helper } = codeceptjs;
const chai = require('chai');
const chaiAsPromised = require('chai-as-promised');
chai.use(chaiAsPromised);
const expect = chai.expect;
class MyHelper extends Helper {
async JavaScriptclick(LocatorVale) {
await browser.waitForAngularEnabled(true);
browser.executeScript("arguments[0].click();", element(by.xpath(LocatorVale)));
}
}
module.exports = MyHelper;
Code.js
helpers: {
MyHelper: {
require: './myhelper_helper.js',
},
Stepfile.js
Scenario('Add a new user group', (I, MyHelper) => {
MyHelper.JavaScriptclick(‘.badged-button.mat-raised-button.mat-primar')
});
If I execute the above code, I get the below error
Add a new user group:
Object of type MyHelper is not defined in container
Please help me to resolve this issue. I need to click on a button, but I.click is not able to locate the button. So I had to use javascriptclick in this case. However I.executescript is also not working. Hence I need to use native protractor commands as it works only this way.
There are no need to call helper by it's name.
All methods are added in actor (I) object
As said in documentation:
Methods of Helper class will be available in tests in I object. This abstracts test scenarios from the implementation and allows easily switching between backends.
https://codecept.io/helpers/#extending-codeceptjs-with-custom-helopers
So, in test:
Scenario('Add a new user group', (I) => {
I.JavaScriptclick(‘.badged-button.mat-raised-button.mat-primar')
});