Related
pragma solidity >=0.6.0 <0.9.0;
//import "#chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; ao importar este contracto o debaixo nao seria necessario mas usamos para ter um melhor entendimento do contrato
interface AggregatorV3Interface {
function decimals() external view returns (uint8);
function description() external view returns (string memory);
function version() external view returns (uint256);
// getRoundData and latestRoundData should both raise "No data present"
// if they do not have data to report, instead of returning unset values
// which could be misinterpreted as actual reported values.
function getRoundData(uint80 _roundId)
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
function latestRoundData()
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
}
contract FundMe {
//track de todos os endereços que interagiram com o contracto
mapping(address => uint256) public addressToAmmountFunded;
address public owner;
//tudo o que é feito nos constructor() é automaticamente aplicado na criaçao do smart contract
constructor() {
//como o primeiro msg.sender é o criador do smart contract fica automaticamente como owner
owner = msg.sender;
}
function fund() public payable {
uint256 minimunUSD = 500 * 10 ** 18; //define que minimunUSD tem de ser 50 mas é preciso multiplicar por 10^18 por estarmos a usar WEI
require(getConversionRate(msg.value) >= minimunUSD, "You need to invest more than 500USD in xxx tokens!"); // Se valor enviado nao for suficiente o ele reverte a transacçao
addressToAmmountFunded[msg.sender] += msg.value;
//contrato que so aceita ETH por isso é necessario fazer uma conversao para um USD conversion rate
}
function getVersion() public view returns (uint256){
AggregatorV3Interface priceFeed = AggregatorV3Interface(0x8A753747A1Fa494EC906cE90E9f37563A8AF630e); // contrato definido encontrado em https://docs.chain.link/docs/ethereum-addresses/ que é ETH/USD
return priceFeed.version();
}
function getPrice() public view returns(uint256){
AggregatorV3Interface priceFeed = AggregatorV3Interface(0x8A753747A1Fa494EC906cE90E9f37563A8AF630e);
(,int256 answer,,,) = priceFeed.latestRoundData(); // O numero de virgulas representam as variaves em latest round data que nao estao a ser usadas
return uint256(answer * 10000000000);
}
function getLatestUpdate() public view returns(uint256){
AggregatorV3Interface priceFeed = AggregatorV3Interface(0x8A753747A1Fa494EC906cE90E9f37563A8AF630e);
(,,,uint256 updatedAt,) = priceFeed.latestRoundData(); // O numero de virgulas representam as variaves em latest round data que nao estao a ser usadas
return updatedAt;
}
// 1000000000
function getConversionRate(uint256 ethAmount) public view returns(uint256){
uint256 ethPrice = getPrice();
uint256 ethAmountInUsd = (ethPrice * ethAmount) / 1000000000000000000 ; //divisao por 10^18 casas decimais do ethereum
return ethAmountInUsd;
}
//este modificador faz com que o codigo de withdraw() só corra depois de executar o require
modifier onlyOwner {
require(msg.sender == owner);
_;
}
function withdraw() payable onlyOwner public {
msg.sender.transfer(address(this).balance);
}
}
In the last function withdraw() im getting the error TypeError: "send" and "transfer" are only available for objects of type "address payable", not "address".
I'm using compiler version 0.8.7 of REMIX IDE
I've double-checked all the smart contracts and I can't see any way to solve it.
What I'm trying to do with this function is check all the value deposited on the smart contract and withdraw it to the owner address being the one who deployed the smart contract
Since you have modified it with onlyOwner, only the owner can call it. So
msg.sender is actually owner.
the owner should be payable. If there is no other bug in your code, this should solve it:
address payable owner;
I'm completely new to Sinon/Jest and unit testing, so I'm kinda lost here. I tried to make a sandbox to declare all my stubs inside it but even after using sandbox.restore() the stub's call count is preserved, so my test fails in the next 'it'.
I wasn't able to stub TypeORM's objects directly, so I decided to create fake objects with only the methods I needed and made TypeORM's getRepository() use my created objects.
I'm not sure if this approach is even correct but looks like my tests are working, I can assert the number of calls and it's parameters, in the second 'it' I can expect that the error thrown equals 'Email já cadastrado' and if I change the expected message the test fails.
The issue is that the number of calls won't reset before the next 'it' block. So I always get an error on the line "sandbox.assert.calledOnceWithExactly(fakeConnection.getRepository, Cidades)" since it's being called twice (once on previous 'it' and a second time on the current block).
If I remove that line I get an error in the assert transaction section, since I expect commitTransaction to not have been called but it was called once (in the previous 'it' block).
Image of my Error
Here is my test:
const sandbox = sinon.createSandbox();
// Simulating TypeORM's QueryRunner
const fakeQueryRunner = {
connect: sandbox.stub().returnsThis(),
startTransaction: sandbox.stub().returnsThis(),
rollbackTransaction: sandbox.stub().returnsThis(),
commitTransaction: sandbox.stub().returnsThis(),
release: sandbox.stub().returnsThis(),
manager: { save: sandbox.stub().returnsThis() },
};
// Simulating TypeORM's Connection
const fakeConnection = {
createQueryRunner: sandbox.stub().returns( fakeQueryRunner ),
getRepository: sandbox.stub().returnsThis(),
findOneOrFail: sandbox.stub().returnsThis(),
}
// I've hidden the mock of my parameters/entities (Usuarios, Senhas, Cidades) since I don't think it's needed to solve the problem.
describe('UserRepository', function () {
let userRepository;
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [
UserRepository,
],
}).compile();
userRepository = module.get<UserRepository>(UserRepository);
});
describe('signUp', function () {
beforeEach(function () {
// typeORM.getConnection() returns my object simulating a Connection.
sandbox.stub(typeorm, 'getConnection').returns( fakeConnection as unknown as typeorm.Connection )
// Stubbing this.create()
sandbox.stub(userRepository, 'create').returns( Usuarios )
});
afterEach(function () {
sandbox.restore();
});
it('successfully signs up the user', async function () {
// Simulating sucessful save transaction
fakeQueryRunner.manager.save.onCall(0).resolves(Usuarios);
fakeQueryRunner.manager.save.onCall(1).resolves(Senhas);
// Calling my method
await userRepository.signUp(mockCredentialsDto);
// First interation, this line works
sandbox.assert.calledOnceWithExactly(fakeConnection.getRepository, Cidades);
// Asserting that transaction was commited
sandbox.assert.calledOnce(fakeQueryRunner.commitTransaction);
sandbox.assert.notCalled(fakeQueryRunner.rollbackTransaction);
});
it('throws a conflic exception as username already exists', async function () {
// Simulating a reject from transaction
fakeQueryRunner.manager.save.onCall(0).rejects({ code: '23505' });
// Calling my method and catching error
try {
await userRepository.signUp(mockCredentialsDto)
} catch (err) {
expect(err).toEqual(new Error('Email já cadastrado'));
}
// Second interation, this line giver ERROR (method called twice)
sandbox.assert.calledOnceWithExactly(fakeConnection.getRepository, Cidades);
// Asserting that transaction was rolled back, this also gives an error since commitTransaction was called once (in the first 'it' block)
sandbox.assert.notCalled(fakeQueryRunner.commitTransaction);
sandbox.assert.calledOnce(fakeQueryRunner.rollbackTransaction);
});
// I will make another describe block here eventually
});
Here is the method I'm testing:
async signUp(authCredentialsDto: AuthCredentialsDto): Promise<signUpMessage> {
const { nome, email, genero, dataNascimento, profissao, organizacao, atuacao, nomeCidade, uf, senha } = authCredentialsDto;
const connection = getConnection();
const user = this.create();
const senhas = new Senhas;
const cidade = await connection.getRepository(Cidades).findOneOrFail({where: { nome: nomeCidade, uf: uf } });
// Set values
user.nome = nome;
user.email = email;
user.genero = genero;
user.dataNascimento = dataNascimento;
user.profissao = profissao;
user.organizacao = organizacao;
user.atuacao = atuacao;
user.idCidade = cidade;
const salt = await bcrypt.genSalt();
senhas.senha = await this.hashPassword(senha, salt)
senhas.idUsuario2 = user;
// Make a transaction to save the data
const queryRunner = connection.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
await queryRunner.manager.save(user);
await queryRunner.manager.save(senhas);
await queryRunner.commitTransaction();
} catch (error) {
if ( error.code === '23505' ) { // Usuário repetido
await queryRunner.rollbackTransaction();
throw new ConflictException('Email já cadastrado')
} else {
await queryRunner.rollbackTransaction();
throw new InternalServerErrorException;
}
} finally {
await queryRunner.release();
}
let success: signUpMessage;
return success;
}
Managed to make it work, I declared my Sinon Sandbox and Objects inside a beforeEach instead of the start of my test.
My test looks like this now:
describe('UserRepository', () => {
let userRepository
let sandbox
let fakeQueryRunner
let fakeConnection
let mockUser: Usuarios
beforeEach(async () => {
sandbox = sinon.createSandbox()
// Cria um objeto QueryRunner fake
fakeQueryRunner = {
connect: sandbox.stub().returnsThis(),
startTransaction: sandbox.stub().returnsThis(),
rollbackTransaction: sandbox.stub().returnsThis(),
commitTransaction: sandbox.stub().returnsThis(),
release: sandbox.stub().returnsThis(),
manager: { save: sandbox.stub().returnsThis() },
}
// Cria um objeto Connection fake (note que o stub de createQueryRunner retorna o objeto fakeQueryRunner )
fakeConnection = {
createQueryRunner: sandbox.stub().returns(fakeQueryRunner),
getRepository: sandbox.stub().returnsThis(),
findOneOrFail: sandbox.stub().returnsThis(),
}
mockUser = {
idUsuario: '1',
atuacao: 'lol',
dataNascimento: '10/10/2021',
email: 'teste#kik.com',
genero: Genero.MASCULINO,
nome: 'Teste Nome',
organizacao: 'Teste org',
profissao: 'Teste Prof',
idCidade: mockCidade,
membros: [],
senhas: mockSenha,
validatePassword: sandbox.stub().returnsThis(),
}
const module = await Test.createTestingModule({
providers: [UserRepository],
}).compile()
userRepository = module.get<UserRepository>(UserRepository)
})
afterEach(() => {
sandbox.restore()
})
describe('signUp', () => {
beforeEach(function () {
// Cria um método falso que retorna o objeto fakeConnection
sandbox.stub(typeorm, 'getConnection').returns((fakeConnection as unknown) as typeorm.Connection)
// Simula o Create de UserRepository
sandbox.stub(userRepository, 'create').returns(Usuarios)
})
it('successfully signs up the user', async () => {
// Salvar Usuário e Senha foi bem sucedido
fakeQueryRunner.manager.save.onCall(0).resolves(Usuarios)
fakeQueryRunner.manager.save.onCall(1).resolves(Senhas)
await userRepository.signUp(mockCredentialsDto)
// Verificando instanciação do repositório
const call1 = fakeConnection.getRepository.onCall(0).resolves(Cidades)
sandbox.assert.calledWith(call1, Cidades)
sandbox.assert.calledOnceWithExactly(fakeConnection.getRepository, Cidades)
// Verificando Transactions
sandbox.assert.calledOnce(fakeQueryRunner.commitTransaction)
sandbox.assert.notCalled(fakeQueryRunner.rollbackTransaction)
})
it('throws a conflic exception as username already exists', async () => {
// Houve um erro em um dos saves, resultando num rollback da transaction
fakeQueryRunner.manager.save.onCall(0).resolves(Usuarios)
fakeQueryRunner.manager.save.onCall(1).rejects({ code: '23505' })
try {
await userRepository.signUp(mockCredentialsDto)
} catch (err) {
expect(err).toEqual(new Error('Email já cadastrado'))
}
// Verificando instanciação do repositório
const call1 = fakeConnection.getRepository.onCall(0).resolves(Cidades)
sandbox.assert.calledWith(call1, Cidades)
sandbox.assert.calledOnceWithExactly(fakeConnection.getRepository, Cidades)
// Verificando Transactions
sandbox.assert.notCalled(fakeQueryRunner.commitTransaction)
sandbox.assert.calledOnce(fakeQueryRunner.rollbackTransaction)
})
})
})
Using Google App Script with GSuite's Directory API. I want to list all users from the root OU. This code pulls all users from the directory and filters out those who are members of sub-OU.
The code.
/**
* LIST USERS FROM ROOT OU
*/
function listUsers() {
var optionalArgs = {
domain: 'mydomain.com',
maxResults: 500,
orderBy: 'givenName'
};
var response = AdminDirectory.Users.list(optionalArgs);
var users = response.users;
var counter = 0;
if (users && users.length > 0) {
Logger.log('Liste des utilisateurs...\n');
for (i = 0; i < users.length; i++) {
var user = users[i];
/* FILTER OUT SUB-OU, PRINT ONLY MEMBERS IN ROOT OU */
if (user.orgUnitPath == '/') {
counter++;
Logger.log('Result #%s', counter.toString());
Logger.log('\nName : %s\nEmail : %s\nOU : %s\n', user.name.fullName, user.primaryEmail, user.orgUnitPath);
}
}
} else {
Logger.log('Nothing found...');
}
}
Now the function listUsers has a cap of 500 results but my organisation has way more users accounts.
I know my problem will be solved by the use of the pageToken parameter and this has been asked time and again. I have been reading through many posts on the subject, and yet, I am not finding any clear explanation and a way to adapt this to App Script.
Any help would be greatly appreciated.
I figured it out
function listUsers(){
var values = [],
users = [],
userListQuery = {},
nextPageToken = '',
listObject = {
customer: 'my_customer',
query: "isSuspended=false",
maxResults: 500
},
i = 0,
j = 0;
do {
if (nextPageToken && nextPageToken !== '') {
listObject.pageToken = nextPageToken;
}
var userListQuery = AdminDirectory.Users.list(listObject);
// Si plus de maxResults retourner à nextPageToken
nextPageToken = userListQuery.nextPageToken;
// Ajout de résultats à la liste
users = users.concat(userListQuery.users);
} while (nextPageToken);
for (i = 0; i < users.length; i++) {
// Filtrer les résultats
if (users[i].orgUnitPath == '/') {
j++;
Logger.log('Résultat #%s', j.toString());
Logger.log('\nNom : %s\nCourriel : %s\nOU : %s\nDépartement : %s\n', users[i].name.fullName, users[i].primaryEmail, users[i].orgUnitPath, users[i].orgDepartment);
}
}
}
I have a REST service deployed on a server and would like to expose it (proxy it) through WSO2 ESB and use the ESB security to secure the access to the service (probably HTTP BASIC authentication that looks by username and password in the ESB user database). I cannot find a good documentation describing how to do it. Could this be done using WSO2 ESB and how?
You can refer this blog post for creating proxy for REST service.To secure the service there are articles on how to secure services. this is one such.
I am adding a new link for securing REST services.
You can use REST API for this purpose, with wso2 ESB 4.0.2 onward it ships with REST API where you can easily invoke them, you can get a comprehensive understanding on how the REST API involoves in WSO2 ESB ref REST API Article
For http basic Authenticathion with users form wso2-esb I use these sequence.
<sequence xmlns="http://ws.apache.org/ns/synapse" name="ValidacionHttpBasica">
<property name="isAuthorized" value="0" scope="default" type="STRING"/>
<class name="org.wso2.security.HttpBasicAuthOpMediator"/>
<switch source="get-property('isAuthorized')">
<case regex="0">
<property name="HTTP_SC" value="401" scope="axis2" type="STRING"/>
<makefault version="soap11">
<code xmlns:soap11Env="http://schemas.xmlsoap.org/soap/envelope/" value="soap11Env:Server"/>
<reason value="Not Authorized"/>
<role/>
</makefault>
<respond/>
</case>
<default/>
</switch>
</sequence>
The code for org.wso2.security.HttpBasicAuthOpMediator (wso2-esb 4.9.0)
package org.wso2.security;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.codec.binary.Base64;
import org.apache.log4j.Logger;
import org.apache.synapse.MessageContext;
import org.apache.synapse.core.axis2.Axis2MessageContext;
import org.apache.synapse.mediators.AbstractMediator;
import org.wso2.carbon.context.CarbonContext;
import org.wso2.carbon.user.api.UserStoreException;
public class HttpBasicAuthOpMediator extends AbstractMediator {
private static final Logger LOGGER = Logger.getLogger(HttpBasicAuthOpMediator.class);
/* (non-Javadoc)
* #see org.apache.synapse.Mediator#mediate(org.apache.synapse.MessageContext)
*/
public boolean mediate(MessageContext msgctx) {
boolean isOK = true;
try {
//trazearDatos(msgctx);
org.apache.axis2.context.MessageContext axis2MessageContext = ((Axis2MessageContext) msgctx)
.getAxis2MessageContext();
Map<String,String> mHeaders = (Map<String,String>)axis2MessageContext.getProperty("TRANSPORT_HEADERS");
// 1 - Validacion de cabeceras de seguridad
String securityHeader = mHeaders.get("Authorization");
if ( securityHeader==null || securityHeader.trim().length()<7 ) {
throw new RuntimeException ("Request sin cabecera de Autorizacion");
}
// 2 - Validacion de usuario-contrasenya
String user = validarUsuario(securityHeader);
// 3 - validacion de operacion asociada a un rol
if (msgctx.getProperty("SECURITY_OPERATION")!=null && msgctx.getProperty("SECURITY_OPERATION").toString().equalsIgnoreCase("1")) {
validarOperacion(user, mHeaders.get("SOAPAction"), msgctx);
}
// todo ha ido bien, esta autorizado
msgctx.setProperty("isAuthorized", "1");
} catch (Exception e) {
LOGGER.info("ERROR VALIDACION USUARIO ..." + e.getMessage() );
//isOK = false;
}
return isOK;
}
/**
* Comprueba que el usuario tiene los roles asociados a la operacion.
* Si el usuario no tiene los roles, lanza un runtimeExcpetion
* #param operacion, que se obtiene del soapAction de la cabecera http
* #param messageContext
*/
private void validarOperacion(String user, String operacion, MessageContext messageContext) {
operacion = operacion.replaceAll("\"", "");
operacion = operacion.replaceAll("'", "");
//obtener los roles asociados a la operacion
if ( messageContext.getProperty("SECURITY_OPERATION_" + operacion)!= null ) {
boolean existeRol = false;
try {
String[] rolesOperation = messageContext.getProperty("SECURITY_OPERATION_" + operacion).toString().split(",");
Map<String,String> mRolesUser = toMap( CarbonContext.getThreadLocalCarbonContext().getUserRealm().getUserStoreManager().getRoleListOfUser(user) );
for (String rol : rolesOperation) {
if (mRolesUser.containsKey(rol)) {
existeRol = true;
break;
}
}
if (!existeRol) {
throw new RuntimeException("Usuario sin role para ejecutar operacion");
}
} catch (Exception e) {
throw new RuntimeException("ValidaRoleOperacion:" + e.getMessage() );
}
}
}
/**
* Valida si la cabecera contiene un usuario-contrsenya valido en wso2.
* Si no lo encuentra lanza un RuntimeExpception.
* #param cabecera http-header que contiene el usuario-contrsenya en base64.
*/
private String validarUsuario(String cabecera) {
String credentials = cabecera.substring(6).trim();
String decodedCredentials = new String(new Base64().decode(credentials.getBytes()));
String userName = decodedCredentials.split(":")[0];
String password = decodedCredentials.split(":")[1];
//CarbonContext ctx = CarbonContext.getCurrentContext();
CarbonContext ctx = CarbonContext.getThreadLocalCarbonContext();
try {
if ( !ctx.getUserRealm().getUserStoreManager().authenticate(userName, password) ) {
throw new RuntimeException("Usuario-contrasenya incorrecto");
}
} catch (UserStoreException e) {
throw new RuntimeException("UserStoreException:" + e.getMessage() );
}
return userName;
}
public void trazearDatos(MessageContext msgctx ) {
try {
System.out.println("....INICIO_TRAZEO DATOS...............");
// CABECERAS HTTP para pbtener operacion, user-password. Es un Map<String, String>
if ( msgctx.getProperty("TRANSPORT_HEADERS") != null ) {
Map<String,String> mHeaders = (Map<String,String>)msgctx.getProperty("TRANSPORT_HEADERS");
for (String key:mHeaders.keySet() ) {
System.out.println("HEADER_HTTP..." + key + "==" + mHeaders.get(key) );
}
} else {
System.out.println("Es nulo TRANSJPPORT_HEADER, casteamos");
org.apache.axis2.context.MessageContext axis2MessageContext = ((Axis2MessageContext) msgctx)
.getAxis2MessageContext();
Map<String,String> mHeaders = (Map<String,String>)axis2MessageContext.getProperty("TRANSPORT_HEADERS");
for (String key:mHeaders.keySet() ) {
System.out.println("(cast) HEADER_HTTP..." + key + "==" + mHeaders.get(key) );
}
}
// PROPERTIES DEL MESSAGE_CONTEXT
String keyMC;
for (Object keyObject : msgctx.getPropertyKeySet() ) {
keyMC = (String)keyObject;
System.out.println("PROPERTIES_CONTEXT..." + keyMC + "==" + msgctx.getProperty(keyMC));
}
// pintamos los roles que tiene asignado el usuario
CarbonContext carbonctx = CarbonContext.getThreadLocalCarbonContext();
String[] roles = carbonctx.getUserRealm().getUserStoreManager().getRoleNames();
for(String role:roles) {
System.out.println("ROLE_WSO2..." + role);
}
System.out.println("....FIN_TRAZEO DATOS...............");
} catch (Exception e) {
LOGGER.debug("ERROR TRAZEANDO DATOS VALIDACION USER:" + e.getMessage() );
}
}
private Map<String,String> toMap(String[] array) {
Map<String,String> mapa = new HashMap<String,String>();
if ( array!=null) {
for (String val : array) {
mapa.put(val, val);
}
}
return mapa;
}
}
Using MSTest how can I verify the exact error message coming from a test method? I know [ExpectedException(typeof(ApplicationException), error msg)] doesn't compare the error message coming from my test method, though in other unit test framework it is doing.
One way to solve this problem is to write my unit test using some try catch block, but again I need to write 4 lines more.
Is there any smartest way to check the error message.
Cheers,
Pritam
You can create your own ExpectedException attribute where you can Assert the message of the Exception that was thrown.
Code
namespace TestProject
{
public sealed class MyExpectedException : ExpectedExceptionBaseAttribute
{
private Type _expectedExceptionType;
private string _expectedExceptionMessage;
public MyExpectedException(Type expectedExceptionType)
{
_expectedExceptionType = expectedExceptionType;
_expectedExceptionMessage = string.Empty;
}
public MyExpectedException(Type expectedExceptionType, string expectedExceptionMessage)
{
_expectedExceptionType = expectedExceptionType;
_expectedExceptionMessage = expectedExceptionMessage;
}
protected override void Verify(Exception exception)
{
Assert.IsNotNull(exception);
Assert.IsInstanceOfType(exception, _expectedExceptionType, "Wrong type of exception was thrown.");
if(!_expectedExceptionMessage.Length.Equals(0))
{
Assert.AreEqual(_expectedExceptionMessage, exception.Message, "Wrong exception message was returned.");
}
}
}
}
Usage
[TestMethod]
[MyExpectedException(typeof(Exception), "Error")]
public void TestMethod()
{
throw new Exception("Error");
}
Use this little helper class:
public static class ExceptionAssert
{
public static void Throws<TException>(Action action, string message)
where TException : Exception
{
try
{
action();
Assert.Fail("Exception of type {0} expected; got none exception", typeof(TException).Name);
}
catch (TException ex)
{
Assert.AreEqual(message, ex.Message);
}
catch (Exception ex)
{
Assert.Fail("Exception of type {0} expected; got exception of type {1}", typeof(TException).Name, ex.GetType().Name);
}
}
}
Usage:
Foo foo = new Foo();
foo.Property = 42;
ExceptionAssert.Throws<InvalidOperationException>(() => foo.DoSomethingCritical(), "You cannot do anything when Property is 42.");
The advantage of explicit catching exceptions is that teh test does not succeed when another member (e.g. during the initialization) throws the exception.
Fluent Assertions (NuGet) has a very "language natural" syntax for defining expectations in unit tests:
objectundertest.Invoking(o => o.MethodUnderTest()).Should().Throw<ExpectedException>()
.WithMessage("the expected error message");
There are multiple variations for checking the error message with any algorithm (Where(e => ...) as well as checking into inner exceptions and their messages.
MSTest v2 supports Assert.Throws and Assert.ThrowsAsync which returns the captured exception.
Here is an article on how to upgrade to MSTest v2: https://blogs.msdn.microsoft.com/devops/2017/09/01/upgrade-to-mstest-v2/
Here is example usage:
var myObject = new MyObject();
var ex = Assert.Throws<ArgumentNullException>(() => myObject.Do(null));
StringAssert.Contains(ex.Message, "Parameter name: myArg");
In MSTest there's no built-in way of doing it. This is about as 'elegant' as it gets:
[TestMethod]
public void Test8()
{
var t = new Thrower();
try
{
t.DoStuffThatThrows();
Assert.Fail("Exception expected.");
}
catch (InvalidOperationException e)
{
Assert.AreEqual("Boo hiss!", e.Message);
}
}
However, you could consider porting xUnit.NET's Assert.Throws API to a custom library - that's what we did.
You could also go to Microsoft Connect an vote on this suggestion.
I was looking for a way to check the presence and type of inner exception with mstest and I found this question. I known it a 2 years old topic but since my solution is not here, let me share it.
To me, the most elegant way to solve the problem is to create a derived attribute, here's mine (sorry but comments and strings are in french, my natural language, but it should be obvious) :
#region Références
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Text.RegularExpressions;
#endregion
namespace MsTestEx
{
/// <summary>
/// Extention de l'attribut ExpectedException permettant de vérifier plus d'éléments (Message, InnerException, ...)
/// </summary>
public class ExpectedExceptionEx
: ExpectedExceptionBaseAttribute
{
#region Variables locales
private Type _ExpectedException = null;
private string _ExpectedMessage = null;
private Type _ExpectedInnerException = null;
private string _ExpectedInnerExceptionMessage = null;
private bool _IsExpectedMessageRegex = false;
private bool _IsExpectedInnerMessageRegex = false;
private bool _AllowDerivedType = false;
private bool _AllowInnerExceptionDerivedType = false;
private bool _CheckExpectedMessage = false;
private bool _CheckInnerExceptionType = false;
private bool _CheckInnerExceptionMessage = false;
#endregion
#region Propriétés
/// <summary>
/// Vérifie que le message de l'exception correspond à celui-ci.
/// </summary>
public string ExpectedMessage
{
get { return _ExpectedMessage; }
set { _ExpectedMessage = value; _CheckExpectedMessage = true; }
}
/// <summary>
/// Vérifie que le message de l'inner-exception correspond à celui-ci.
/// </summary>
public string ExpectedInnerExceptionMessage
{
get { return _ExpectedInnerExceptionMessage; }
set { _ExpectedInnerExceptionMessage = value; _CheckInnerExceptionMessage = true; }
}
/// <summary>
/// Vérifie que l'exception possède bien une inner-exception du type spécifié.
/// Spécifier "null" pour vérifier l'absence d'inner-exception.
/// </summary>
public Type ExpectedInnerException
{
get { return _ExpectedInnerException; }
set { _ExpectedInnerException = value; _CheckInnerExceptionType = true; }
}
/// <summary>
/// Indique si le message attendu est exprimé via une expression rationnelle.
/// </summary>
public bool IsExpectedMessageRegex
{
get { return _IsExpectedMessageRegex; }
set { _IsExpectedMessageRegex = value; }
}
/// <summary>
/// Indique si le message attendu de l'inner-exception est exprimé via une expression rationnelle.
/// </summary>
public bool IsExpectedInnerMessageRegex
{
get { return _IsExpectedInnerMessageRegex; }
set { _IsExpectedInnerMessageRegex = value; }
}
/// <summary>
/// Indique si les exceptions dérivées sont acceptées.
/// </summary>
public bool AllowDerivedType
{
get { return _AllowDerivedType; }
set { _AllowDerivedType = value; }
}
/// <summary>
/// Indique si les inner-exceptions dérivées sont acceptées.
/// </summary>
public bool AllowInnerExceptionDerivedType
{
get { return _AllowInnerExceptionDerivedType; }
set { _AllowInnerExceptionDerivedType = value; }
}
#endregion
#region Constructeurs
/// <summary>
/// Indique le type d'exception attendu par le test.
/// </summary>
/// <param name="expectedException">Type de l'exception attendu.</param>
public ExpectedExceptionEx(Type expectedException)
{
_ExpectedException = expectedException;
}
#endregion
#region Méthodes
/// <summary>
/// Effectue la vérification.
/// </summary>
/// <param name="exception">Exception levée.</param>
protected override void Verify(Exception exception)
{
Assert.IsNotNull(exception); // Pas eu d'exception, ce n'est pas normal
// Vérification du type de l'exception
Type actualType = exception.GetType();
if (_AllowDerivedType) Assert.IsTrue(_ExpectedException.IsAssignableFrom(actualType), "L'exception reçue n'est pas du type spécifié ni d'un type dérivé.");
else Assert.AreEqual(_ExpectedException, actualType, "L'exception reçue n'est pas du type spécifié.");
// Vérification du message de l'exception
if (_CheckExpectedMessage)
{
if (_IsExpectedMessageRegex)
Assert.IsTrue(Regex.IsMatch(exception.Message, _ExpectedMessage), "Le message de l'exception ne correspond pas à l'expression rationnelle");
else
{
string s1, s2;
if (exception.Message.Length > _ExpectedMessage.Length)
{
s1 = exception.Message;
s2 = _ExpectedMessage;
}
else
{
s1 = _ExpectedMessage;
s2 = exception.Message;
}
Assert.IsTrue(s1.Contains(s2), "Le message de l'exception ne contient pas et n'est pas contenu par le message attendu.");
}
}
if (_CheckInnerExceptionType)
{
if (_ExpectedInnerException == null) Assert.IsNotNull(exception.InnerException);
else
{
// Vérification du type de l'exception
actualType = exception.InnerException.GetType();
if (_AllowInnerExceptionDerivedType) Assert.IsTrue(_ExpectedInnerException.IsAssignableFrom(actualType), "L'inner-exception reçue n'est pas du type spécifié ni d'un type dérivé.");
else Assert.AreEqual(_ExpectedInnerException, actualType, "L'inner-exception reçue n'est pas du type spécifié.");
}
}
if (_CheckInnerExceptionMessage)
{
Assert.IsNotNull(exception.InnerException);
if (_IsExpectedInnerMessageRegex)
Assert.IsTrue(Regex.IsMatch(exception.InnerException.Message, _ExpectedInnerExceptionMessage), "Le message de l'exception ne correspond pas à l'expression rationnelle");
else
{
string s1, s2;
if (exception.InnerException.Message.Length > _ExpectedInnerExceptionMessage.Length)
{
s1 = exception.InnerException.Message;
s2 = _ExpectedInnerExceptionMessage;
}
else
{
s1 = _ExpectedInnerExceptionMessage;
s2 = exception.InnerException.Message;
}
Assert.IsTrue(s1.Contains(s2), "Le message de l'inner-exception ne contient pas et n'est pas contenu par le message attendu.");
}
}
}
#endregion
}
}
Now, use this attribute with named parameters instead of the "ExpectedException". With my attribute you can check if there is an inner exception, message of the exception and inner exception, use a regex to match messages, etc...
You can adapt as you want.
The annoyance with annotations and try/catch blocks is that you don't have a clean separation between the ACT and ASSERT phases of the test. A simpler appraoch is to "capture" the exception as part of the ACT phase using a utitlity routine such as:
public static class Catch
{
public static Exception Exception(Action action)
{
Exception exception = null;
try
{
action();
}
catch (Exception ex)
{
exception = ex;
}
return exception;
}
}
This allows you to do:
// ACT
var actualException = Catch.Exception(() => DoSomething())
// ASSERT
Assert.IsNotNull(actualException, "No exception thrown");
Assert.IsInstanceOfType(actualException, expectedType);
Assert.AreEqual(expectedExceptionMessage, actualException.Message);
With MSTest, you can't do this.
You already know the solution to this problem: assert the message of an exception in a catch block.
MbUnit can also do this:
[Test]
[Row(ExpectedExceptionMessage="my message")]
void TestBlah(...
This code does it in the async/await scenario:
Code Reference
public async static Task AssertThrowsAsync<T>(Task task, string expectedMessage) where T : Exception
{
try
{
await task;
}
catch (Exception ex)
{
if (ex is T)
{
Assert.AreEqual(expectedMessage, ex.Message);
return;
}
Assert.Fail($"Expection exception type: {typeof(T)} Actual type: {ex.GetType()}");
}
Assert.Fail($"No exception thrown");
}
Example Usage:
Code Reference
[TestMethod]
public async Task TestBadRequestThrowsHttpStatusCodeException()
{
var mockHttp = new MockHttpMessageHandler();
const HttpStatusCode statusCode = HttpStatusCode.BadRequest;
mockHttp.When("https://restcountries.eu/rest/v2/")
.Respond(statusCode, "application/json", JsonConvert.SerializeObject(new { Message = "Test", ErrorCode = 100 }));
var httpClient = mockHttp.ToHttpClient();
var factory = new SingletonHttpClientFactory(httpClient);
var baseUri = new Uri("https://restcountries.eu/rest/v2/");
var client = new Client(new NewtonsoftSerializationAdapter(), httpClientFactory: factory, baseUri: baseUri, logger: _logger.Object);
await AssertThrowsAsync<HttpStatusException>(client.GetAsync<List<RestCountry>>(), Messages.GetErrorMessageNonSuccess((int)statusCode, baseUri));
}
This is the equivalent for synchronous situations
public static void AssertThrows<T>(Action action, string expectedMessage) where T : Exception
{
try
{
action.Invoke();
}
catch (Exception ex)
{
if (ex is T)
{
Assert.AreEqual(expectedMessage, ex.Message);
return;
}
Assert.Fail($"Expection exception type: {typeof(T)} Actual type: {ex.GetType()}");
}
Assert.Fail($"No exception thrown");
}
Note: this asserts that the exception inherits from a given type. If you want to check for the specific type you should check for Type equality instead of using the is operator.
Update: Oops.. see that you want this in MSTest. Sorry. Speed read & Misled by your title.
Try this extension project from Callum Hibbert and see if it works.
Old response:
You can do this with NUnit 2.4 and above.
See documentation of ExpectedException here
[ExpectedException( typeof( ArgumentException), ExpectedMessage="unspecified", MatchType=MessageMatch.Contains )]
public void TestMethod()
{
...
MatchType can be Exact (default), Contains or Regex.. which pretty much handles the 80% use-case. There is also an advanced exception handler method approach if the verification gets too complex.. never used it personally.. didn't need it yet.